<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"><title>SpringBoot2入门-02 | 无人深空</title><meta name="keywords" content="SpringBoot2"><meta name="author" content="zcm"><meta name="copyright" content="zcm"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="description" content="45、web实验-抽取公共页面官方文档 - Template Layout  公共页面&#x2F;templates&#x2F;common.html  &lt;!DOCTYPE html&gt;&lt;html lang&#x3D;&quot;en&quot; xmlns:th&#x3D;&quot;http:&#x2F;&#x2F;www.thymeleaf.org&quot;&gt;&lt;!--注意要添加xmlns:th才能添加thymeleaf的">
<meta property="og:type" content="article">
<meta property="og:title" content="SpringBoot2入门-02">
<meta property="og:url" content="https://gitee.com/zcmmmm/zcmmmm/2022/09/23/SpringBoot2%E5%85%A5%E9%97%A8-02/index.html">
<meta property="og:site_name" content="无人深空">
<meta property="og:description" content="45、web实验-抽取公共页面官方文档 - Template Layout  公共页面&#x2F;templates&#x2F;common.html  &lt;!DOCTYPE html&gt;&lt;html lang&#x3D;&quot;en&quot; xmlns:th&#x3D;&quot;http:&#x2F;&#x2F;www.thymeleaf.org&quot;&gt;&lt;!--注意要添加xmlns:th才能添加thymeleaf的">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Spring/Spring.jpg">
<meta property="article:published_time" content="2022-09-23T04:09:23.000Z">
<meta property="article:modified_time" content="2022-09-23T04:11:29.524Z">
<meta property="article:author" content="zcm">
<meta property="article:tag" content="SpringBoot2">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Spring/Spring.jpg"><link rel="shortcut icon" href="/img/favicon.png"><link rel="canonical" href="https://gitee.com/zcmmmm/zcmmmm/2022/09/23/SpringBoot2%E5%85%A5%E9%97%A8-02/"><link rel="preconnect" href="//cdn.jsdelivr.net"/><link rel="preconnect" href="//busuanzi.ibruce.info"/><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/node-snackbar/dist/snackbar.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox.min.css" media="print" onload="this.media='all'"><script>const GLOBAL_CONFIG = { 
  root: '/',
  algolia: undefined,
  localSearch: {"path":"/search.xml","preload":false,"languages":{"hits_empty":"找不到您查询的内容：${query}"}},
  translate: undefined,
  noticeOutdate: undefined,
  highlight: {"plugin":"highlighjs","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":200},
  copy: {
    success: '复制成功',
    error: '复制错误',
    noSupport: '浏览器不支持'
  },
  relativeDate: {
    homepage: false,
    post: false
  },
  runtime: '天',
  date_suffix: {
    just: '刚刚',
    min: '分钟前',
    hour: '小时前',
    day: '天前',
    month: '个月前'
  },
  copyright: undefined,
  lightbox: 'fancybox',
  Snackbar: {"chs_to_cht":"你已切换为繁体","cht_to_chs":"你已切换为简体","day_to_night":"你已切换为深色模式","night_to_day":"你已切换为浅色模式","bgLight":"#49b1f5","bgDark":"#1f1f1f","position":"bottom-left"},
  source: {
    justifiedGallery: {
      js: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery/dist/fjGallery.min.js',
      css: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery/dist/fjGallery.min.css'
    }
  },
  isPhotoFigcaption: false,
  islazyload: false,
  isAnchor: false
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
  title: 'SpringBoot2入门-02',
  isPost: true,
  isHome: false,
  isHighlightShrink: false,
  isToc: true,
  postUpdate: '2022-09-23 12:11:29'
}</script><noscript><style type="text/css">
  #nav {
    opacity: 1
  }
  .justified-gallery img {
    opacity: 1
  }

  #recent-posts time,
  #post-meta time {
    display: inline !important
  }
</style></noscript><script>(win=>{
    win.saveToLocal = {
      set: function setWithExpiry(key, value, ttl) {
        if (ttl === 0) return
        const now = new Date()
        const expiryDay = ttl * 86400000
        const item = {
          value: value,
          expiry: now.getTime() + expiryDay,
        }
        localStorage.setItem(key, JSON.stringify(item))
      },

      get: function getWithExpiry(key) {
        const itemStr = localStorage.getItem(key)

        if (!itemStr) {
          return undefined
        }
        const item = JSON.parse(itemStr)
        const now = new Date()

        if (now.getTime() > item.expiry) {
          localStorage.removeItem(key)
          return undefined
        }
        return item.value
      }
    }
  
    win.getScript = url => new Promise((resolve, reject) => {
      const script = document.createElement('script')
      script.src = url
      script.async = true
      script.onerror = reject
      script.onload = script.onreadystatechange = function() {
        const loadState = this.readyState
        if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
        script.onload = script.onreadystatechange = null
        resolve()
      }
      document.head.appendChild(script)
    })
  
      win.activateDarkMode = function () {
        document.documentElement.setAttribute('data-theme', 'dark')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
        }
      }
      win.activateLightMode = function () {
        document.documentElement.setAttribute('data-theme', 'light')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
        }
      }
      const t = saveToLocal.get('theme')
    
          if (t === 'dark') activateDarkMode()
          else if (t === 'light') activateLightMode()
        
      const asideStatus = saveToLocal.get('aside-status')
      if (asideStatus !== undefined) {
        if (asideStatus === 'hide') {
          document.documentElement.classList.add('hide-aside')
        } else {
          document.documentElement.classList.remove('hide-aside')
        }
      }
    
    const detectApple = () => {
      if(/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){
        document.documentElement.classList.add('apple')
      }
    }
    detectApple()
    })(window)</script><link rel="stylesheet" href="/css/categories.css?v1"><link rel="stylesheet" href="/css/navigation.css?v1"><link rel="stylesheet" href="/css/custom.css?v1"><meta name="generator" content="Hexo 6.3.0"></head><body><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="avatar-img is-center"><img src="https://s2.loli.net/2022/09/22/36LN4kcjYOd9HT8.jpg" onerror="onerror=null;src='/img/friend_404.gif'" alt="avatar"/></div><div class="sidebar-site-data site-data is-center"><a href="/archives/"><div class="headline">文章</div><div class="length-num">43</div></a><a href="/tags/"><div class="headline">标签</div><div class="length-num">25</div></a><a href="/categories/"><div class="headline">分类</div><div class="length-num">11</div></a></div><hr/><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/archives/"><i class="fa-fw fas fa-archive"></i><span> 时间轴</span></a></div><div class="menus_item"><a class="site-page" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/categories/"><i class="fa-fw fas fa-folder-open"></i><span> 分类</span></a></div></div></div></div><div class="post" id="body-wrap"><header class="not-top-img" id="page-header"><nav id="nav"><span id="blog_name"><a id="site-name" href="/">无人深空</a></span><div id="menus"></div><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/archives/"><i class="fa-fw fas fa-archive"></i><span> 时间轴</span></a></div><div class="menus_item"><a class="site-page" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/categories/"><i class="fa-fw fas fa-folder-open"></i><span> 分类</span></a></div></div><div id="nav-right"><div id="search-button"><a class="site-page social-icon search"><i class="fas fa-search fa-fw"></i></a></div><div id="toggle-menu"><a class="site-page"><i class="fas fa-bars fa-fw"></i></a></div></div></nav></header><main class="layout" id="content-inner"><div id="post"><div id="post-info"><h1 class="post-title">SpringBoot2入门-02</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="far fa-calendar-alt fa-fw post-meta-icon"></i><span class="post-meta-label">发表于</span><time class="post-meta-date-created" datetime="2022-09-23T04:09:23.000Z" title="发表于 2022-09-23 12:09:23">2022-09-23</time><span class="post-meta-separator">|</span><i class="fas fa-history fa-fw post-meta-icon"></i><span class="post-meta-label">更新于</span><time class="post-meta-date-updated" datetime="2022-09-23T04:11:29.524Z" title="更新于 2022-09-23 12:11:29">2022-09-23</time></span><span class="post-meta-categories"><span class="post-meta-separator">|</span><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/categories/Spring/">Spring</a></span></div><div class="meta-secondline"><span class="post-meta-separator">|</span><span class="post-meta-wordcount"><i class="far fa-file-word fa-fw post-meta-icon"></i><span class="post-meta-label">字数总计:</span><span class="word-count">20.6k</span><span class="post-meta-separator">|</span><i class="far fa-clock fa-fw post-meta-icon"></i><span class="post-meta-label">阅读时长:</span><span>100分钟</span></span><span class="post-meta-separator">|</span><span class="post-meta-pv-cv" id="" data-flag-title="SpringBoot2入门-02"><i class="far fa-eye fa-fw post-meta-icon"></i><span class="post-meta-label">阅读量:</span><span id="busuanzi_value_page_pv"><i class="fa-solid fa-spinner fa-spin"></i></span></span></div></div></div><article class="post-content" id="article-container"><h2 id="45、web实验-抽取公共页面"><a href="#45、web实验-抽取公共页面" class="headerlink" title="45、web实验-抽取公共页面"></a>45、web实验-抽取公共页面</h2><p><a target="_blank" rel="noopener" href="https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#template-layout">官方文档 - Template Layout</a></p>
<ul>
<li>公共页面<code>/templates/common.html</code></li>
</ul>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span> <span class="attr">xmlns:th</span>=<span class="string">&quot;http://www.thymeleaf.org&quot;</span>&gt;</span><span class="comment">&lt;!--注意要添加xmlns:th才能添加thymeleaf的标签--&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span> <span class="attr">th:fragment</span>=<span class="string">&quot;commonheader&quot;</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!--common--&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">link</span> <span class="attr">href</span>=<span class="string">&quot;css/style.css&quot;</span> <span class="attr">th:href</span>=<span class="string">&quot;@&#123;/css/style.css&#125;&quot;</span> <span class="attr">rel</span>=<span class="string">&quot;stylesheet&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">link</span> <span class="attr">href</span>=<span class="string">&quot;css/style-responsive.css&quot;</span> <span class="attr">th:href</span>=<span class="string">&quot;@&#123;/css/style-responsive.css&#125;&quot;</span> <span class="attr">rel</span>=<span class="string">&quot;stylesheet&quot;</span>&gt;</span></span><br><span class="line">    ...</span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- left side start--&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;leftmenu&quot;</span> <span class="attr">class</span>=<span class="string">&quot;left-side sticky-left-side&quot;</span>&gt;</span></span><br><span class="line">	...</span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;left-side-inner&quot;</span>&gt;</span></span><br><span class="line">		...</span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!--sidebar nav start--&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">ul</span> <span class="attr">class</span>=<span class="string">&quot;nav nav-pills nav-stacked custom-nav&quot;</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">li</span>&gt;</span><span class="tag">&lt;<span class="name">a</span> <span class="attr">th:href</span>=<span class="string">&quot;@&#123;/main.html&#125;&quot;</span>&gt;</span><span class="tag">&lt;<span class="name">i</span> <span class="attr">class</span>=<span class="string">&quot;fa fa-home&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">i</span>&gt;</span> <span class="tag">&lt;<span class="name">span</span>&gt;</span>Dashboard<span class="tag">&lt;/<span class="name">span</span>&gt;</span><span class="tag">&lt;/<span class="name">a</span>&gt;</span><span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">            ...</span><br><span class="line">            <span class="tag">&lt;<span class="name">li</span> <span class="attr">class</span>=<span class="string">&quot;menu-list nav-active&quot;</span>&gt;</span><span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;#&quot;</span>&gt;</span><span class="tag">&lt;<span class="name">i</span> <span class="attr">class</span>=<span class="string">&quot;fa fa-th-list&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">i</span>&gt;</span> <span class="tag">&lt;<span class="name">span</span>&gt;</span>Data Tables<span class="tag">&lt;/<span class="name">span</span>&gt;</span><span class="tag">&lt;/<span class="name">a</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">ul</span> <span class="attr">class</span>=<span class="string">&quot;sub-menu-list&quot;</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">li</span>&gt;</span><span class="tag">&lt;<span class="name">a</span> <span class="attr">th:href</span>=<span class="string">&quot;@&#123;/basic_table&#125;&quot;</span>&gt;</span> Basic Table<span class="tag">&lt;/<span class="name">a</span>&gt;</span><span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">li</span>&gt;</span><span class="tag">&lt;<span class="name">a</span> <span class="attr">th:href</span>=<span class="string">&quot;@&#123;/dynamic_table&#125;&quot;</span>&gt;</span> Advanced Table<span class="tag">&lt;/<span class="name">a</span>&gt;</span><span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">li</span>&gt;</span><span class="tag">&lt;<span class="name">a</span> <span class="attr">th:href</span>=<span class="string">&quot;@&#123;/responsive_table&#125;&quot;</span>&gt;</span> Responsive Table<span class="tag">&lt;/<span class="name">a</span>&gt;</span><span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">li</span>&gt;</span><span class="tag">&lt;<span class="name">a</span> <span class="attr">th:href</span>=<span class="string">&quot;@&#123;/editable_table&#125;&quot;</span>&gt;</span> Edit Table<span class="tag">&lt;/<span class="name">a</span>&gt;</span><span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">            ...</span><br><span class="line">        <span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!--sidebar nav end--&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- left side end--&gt;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">&lt;!-- header section start--&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">th:fragment</span>=<span class="string">&quot;headermenu&quot;</span> <span class="attr">class</span>=<span class="string">&quot;header-section&quot;</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!--toggle button start--&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">a</span> <span class="attr">class</span>=<span class="string">&quot;toggle-btn&quot;</span>&gt;</span><span class="tag">&lt;<span class="name">i</span> <span class="attr">class</span>=<span class="string">&quot;fa fa-bars&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">i</span>&gt;</span><span class="tag">&lt;/<span class="name">a</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!--toggle button end--&gt;</span></span><br><span class="line">	...</span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- header section end--&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;commonscript&quot;</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- Placed js at the end of the document so the pages load faster --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">th:src</span>=<span class="string">&quot;@&#123;/js/jquery-1.10.2.min.js&#125;&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">th:src</span>=<span class="string">&quot;@&#123;/js/jquery-ui-1.9.2.custom.min.js&#125;&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">th:src</span>=<span class="string">&quot;@&#123;/js/jquery-migrate-1.2.1.min.js&#125;&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">th:src</span>=<span class="string">&quot;@&#123;/js/bootstrap.min.js&#125;&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">th:src</span>=<span class="string">&quot;@&#123;/js/modernizr.min.js&#125;&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">th:src</span>=<span class="string">&quot;@&#123;/js/jquery.nicescroll.js&#125;&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!--common scripts for all pages--&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">th:src</span>=<span class="string">&quot;@&#123;/js/scripts.js&#125;&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>



<ul>
<li><code>/templates/table/basic_table.html</code></li>
</ul>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span> <span class="attr">xmlns:th</span>=<span class="string">&quot;http://www.thymeleaf.org&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;utf-8&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">&quot;viewport&quot;</span> <span class="attr">content</span>=<span class="string">&quot;width=device-width, initial-scale=1.0, maximum-scale=1.0&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">&quot;description&quot;</span> <span class="attr">content</span>=<span class="string">&quot;&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">&quot;author&quot;</span> <span class="attr">content</span>=<span class="string">&quot;ThemeBucket&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">&quot;shortcut icon&quot;</span> <span class="attr">href</span>=<span class="string">&quot;#&quot;</span> <span class="attr">type</span>=<span class="string">&quot;image/png&quot;</span>&gt;</span></span><br><span class="line"></span><br><span class="line">  <span class="tag">&lt;<span class="name">title</span>&gt;</span>Basic Table<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">th:include</span>=<span class="string">&quot;common :: commonheader&quot;</span>&gt;</span> <span class="tag">&lt;/<span class="name">div</span>&gt;</span><span class="comment">&lt;!--将common.html的代码段 插进来--&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span> <span class="attr">class</span>=<span class="string">&quot;sticky-header&quot;</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">section</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">th:replace</span>=<span class="string">&quot;common :: #leftmenu&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">&lt;!-- main content start--&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;main-content&quot;</span> &gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">th:replace</span>=<span class="string">&quot;common :: headermenu&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">        ...</span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- main content end--&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">section</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">&lt;!-- Placed js at the end of the document so the pages load faster --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">th:replace</span>=<span class="string">&quot;common :: #commonscript&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p><a target="_blank" rel="noopener" href="https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#difference-between-thinsert-and-threplace-and-thinclude">Difference between <code>th:insert</code> and <code>th:replace</code> (and <code>th:include</code>)</a></p>
<h2 id="46、web实验-遍历数据与页面bug修改"><a href="#46、web实验-遍历数据与页面bug修改" class="headerlink" title="46、web实验-遍历数据与页面bug修改"></a>46、web实验-遍历数据与页面bug修改</h2><p>控制层代码：</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@GetMapping(&quot;/dynamic_table&quot;)</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">dynamic_table</span><span class="params">(Model model)</span>&#123;</span><br><span class="line">    <span class="comment">//表格内容的遍历</span></span><br><span class="line">    List&lt;User&gt; users = Arrays.asList(<span class="keyword">new</span> <span class="title class_">User</span>(<span class="string">&quot;zhangsan&quot;</span>, <span class="string">&quot;123456&quot;</span>),</span><br><span class="line">                                     <span class="keyword">new</span> <span class="title class_">User</span>(<span class="string">&quot;lisi&quot;</span>, <span class="string">&quot;123444&quot;</span>),</span><br><span class="line">                                     <span class="keyword">new</span> <span class="title class_">User</span>(<span class="string">&quot;haha&quot;</span>, <span class="string">&quot;aaaaa&quot;</span>),</span><br><span class="line">                                     <span class="keyword">new</span> <span class="title class_">User</span>(<span class="string">&quot;hehe &quot;</span>, <span class="string">&quot;aaddd&quot;</span>));</span><br><span class="line">    model.addAttribute(<span class="string">&quot;users&quot;</span>,users);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;table/dynamic_table&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>页面代码：</p>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">table</span> <span class="attr">class</span>=<span class="string">&quot;display table table-bordered&quot;</span> <span class="attr">id</span>=<span class="string">&quot;hidden-table-info&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">thead</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">tr</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">th</span>&gt;</span>#<span class="tag">&lt;/<span class="name">th</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">th</span>&gt;</span>用户名<span class="tag">&lt;/<span class="name">th</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">th</span>&gt;</span>密码<span class="tag">&lt;/<span class="name">th</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">tr</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">thead</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">tbody</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">tr</span> <span class="attr">class</span>=<span class="string">&quot;gradeX&quot;</span> <span class="attr">th:each</span>=<span class="string">&quot;user,stats:$&#123;users&#125;&quot;</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">td</span> <span class="attr">th:text</span>=<span class="string">&quot;$&#123;stats.count&#125;&quot;</span>&gt;</span>Trident<span class="tag">&lt;/<span class="name">td</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">td</span> <span class="attr">th:text</span>=<span class="string">&quot;$&#123;user.userName&#125;&quot;</span>&gt;</span>Internet<span class="tag">&lt;/<span class="name">td</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">td</span> &gt;</span>[[$&#123;user.password&#125;]]<span class="tag">&lt;/<span class="name">td</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">tr</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">tbody</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">table</span>&gt;</span></span><br></pre></td></tr></table></figure>



<h2 id="47、视图解析-【源码分析】-视图解析器与视图"><a href="#47、视图解析-【源码分析】-视图解析器与视图" class="headerlink" title="47、视图解析-【源码分析】-视图解析器与视图"></a>47、视图解析-【源码分析】-视图解析器与视图</h2><p><strong>视图解析原理流程</strong>：</p>
<ol>
<li><p>目标方法处理的过程中（阅读<code>DispatcherServlet</code>源码），所有数据都会被放在 <code>ModelAndViewContainer</code> 里面，其中包括数据和视图地址。</p>
</li>
<li><p>方法的参数是一个自定义类型对象（从请求参数中确定的），把他重新放在 <code>ModelAndViewContainer</code> 。</p>
</li>
<li><p>任何目标方法执行完成以后都会返回<code>ModelAndView</code>（数据和视图地址）。</p>
</li>
<li><p><code>processDispatchResult()</code>处理派发结果（页面改如何响应）</p>
<ul>
<li><p><code>render(mv, request, response);</code> 进行页面渲染逻辑</p>
<ul>
<li>根据方法的<code>String</code>返回值得到 <code>View</code> 对象【定义了页面的渲染逻辑】</li>
</ul>
<ol>
<li>所有的视图解析器尝试是否能根据当前返回值得到<code>View</code>对象</li>
<li>得到了  <code>redirect:/main.html --&gt; Thymeleaf new RedirectView()</code>。</li>
<li><code>ContentNegotiationViewResolver</code> 里面包含了下面所有的视图解析器，内部还是利用下面所有视图解析器得到视图对象。</li>
<li><code>view.render(mv.getModelInternal(), request, response);</code>  视图对象调用自定义的render进行页面渲染工作。</li>
</ol>
<ul>
<li><code>RedirectView</code> 如何渲染【重定向到一个页面】</li>
<li>获取目标url地址	</li>
<li><code>response.sendRedirect(encodedURL);</code></li>
</ul>
</li>
</ul>
</li>
</ol>
<p><strong>视图解析</strong>：<br>    - 返回值以 <code>forward:</code> 开始： <code>new InternalResourceView(forwardUrl);</code> –&gt;  转发<code>request.getRequestDispatcher(path).forward(request, response);</code><br>    - 返回值以 <code>redirect:</code> 开始： <code>new RedirectView()</code> –&gt; render就是重定向<br>    - 返回值是普通字符串：<code>new ThymeleafView()</code>—&gt;</p>
<hr>
<p>阅读源码：最好自己在IDE，打断点，且Debug模式运行实例，这样比较没那么沉闷。</p>
<h2 id="48、拦截器-登录检查与静态资源放行"><a href="#48、拦截器-登录检查与静态资源放行" class="headerlink" title="48、拦截器-登录检查与静态资源放行"></a>48、拦截器-登录检查与静态资源放行</h2><ol>
<li><p>编写一个拦截器实现<code>HandlerInterceptor</code>接口</p>
</li>
<li><p>拦截器注册到容器中（实现<code>WebMvcConfigurer</code>的<code>addInterceptors()</code>）</p>
</li>
<li><p>指定拦截规则（<span style="color:red;">注意</span>，如果是拦截所有，静态资源也会被拦截】</p>
</li>
</ol>
<p>编写一个实现<code>HandlerInterceptor</code>接口的拦截器：</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LoginInterceptor</span> <span class="keyword">implements</span> <span class="title class_">HandlerInterceptor</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 目标方法执行之前</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">preHandle</span><span class="params">(HttpServletRequest request, HttpServletResponse response, Object handler)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line"></span><br><span class="line">        <span class="type">String</span> <span class="variable">requestURI</span> <span class="operator">=</span> request.getRequestURI();</span><br><span class="line">        log.info(<span class="string">&quot;preHandle拦截的请求路径是&#123;&#125;&quot;</span>,requestURI);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//登录检查逻辑</span></span><br><span class="line">        <span class="type">HttpSession</span> <span class="variable">session</span> <span class="operator">=</span> request.getSession();</span><br><span class="line"></span><br><span class="line">        <span class="type">Object</span> <span class="variable">loginUser</span> <span class="operator">=</span> session.getAttribute(<span class="string">&quot;loginUser&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span>(loginUser != <span class="literal">null</span>)&#123;</span><br><span class="line">            <span class="comment">//放行</span></span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//拦截住。未登录。跳转到登录页</span></span><br><span class="line">        request.setAttribute(<span class="string">&quot;msg&quot;</span>,<span class="string">&quot;请先登录&quot;</span>);</span><br><span class="line"><span class="comment">//        re.sendRedirect(&quot;/&quot;);</span></span><br><span class="line">        request.getRequestDispatcher(<span class="string">&quot;/&quot;</span>).forward(request,response);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 目标方法执行完成以后</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">postHandle</span><span class="params">(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        log.info(<span class="string">&quot;postHandle执行&#123;&#125;&quot;</span>,modelAndView);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 页面渲染以后</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">afterCompletion</span><span class="params">(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        log.info(<span class="string">&quot;afterCompletion执行异常&#123;&#125;&quot;</span>,ex);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>拦截器注册到容器中 &amp;&amp; 指定拦截规则：</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AdminWebConfig</span> <span class="keyword">implements</span> <span class="title class_">WebMvcConfigurer</span>&#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addInterceptors</span><span class="params">(InterceptorRegistry registry)</span> &#123;</span><br><span class="line">        registry.addInterceptor(<span class="keyword">new</span> <span class="title class_">LoginInterceptor</span>())<span class="comment">//拦截器注册到容器中</span></span><br><span class="line">                .addPathPatterns(<span class="string">&quot;/**&quot;</span>)  <span class="comment">//所有请求都被拦截包括静态资源</span></span><br><span class="line">                .excludePathPatterns(<span class="string">&quot;/&quot;</span>,<span class="string">&quot;/login&quot;</span>,<span class="string">&quot;/css/**&quot;</span>,<span class="string">&quot;/fonts/**&quot;</span>,<span class="string">&quot;/images/**&quot;</span>,</span><br><span class="line">                        <span class="string">&quot;/js/**&quot;</span>,<span class="string">&quot;/aa/**&quot;</span>); <span class="comment">//放行的请求</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h2 id="49、拦截器-【源码分析】-拦截器的执行时机和原理"><a href="#49、拦截器-【源码分析】-拦截器的执行时机和原理" class="headerlink" title="49、拦截器-【源码分析】-拦截器的执行时机和原理"></a>49、拦截器-【源码分析】-拦截器的执行时机和原理</h2><ol>
<li>根据当前请求，找到<code>HandlerExecutionChain</code>（可以处理请求的handler以及handler的所有 拦截器）</li>
<li>先来顺序执行 所有拦截器的 <code>preHandle()</code>方法。<ul>
<li>如果当前拦截器<code>preHandle()</code>返回为<code>true</code>。则执行下一个拦截器的<code>preHandle()</code></li>
<li>如果当前拦截器返回为<code>false</code>。直接倒序执行所有已经执行了的拦截器的  <code>afterCompletion();</code>。</li>
</ul>
</li>
<li>如果任何一个拦截器返回<code>false</code>，直接跳出不执行目标方法。</li>
<li>所有拦截器都返回<code>true</code>，才执行目标方法。</li>
<li>倒序执行所有拦截器的<code>postHandle()</code>方法。</li>
<li>前面的步骤有任何异常都会直接倒序触发 <code>afterCompletion()</code>。</li>
<li>页面成功渲染完成以后，也会倒序触发 <code>afterCompletion()</code>。</li>
</ol>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Spring/20210205011212637.png" alt="在这里插入图片描述"></p>
<p><code>DispatcherServlet</code>中涉及到<code>HandlerInterceptor</code>的地方：</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DispatcherServlet</span> <span class="keyword">extends</span> <span class="title class_">FrameworkServlet</span> &#123;</span><br><span class="line">    </span><br><span class="line">    ...</span><br><span class="line">    </span><br><span class="line">	<span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">doDispatch</span><span class="params">(HttpServletRequest request, HttpServletResponse response)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">		<span class="type">HttpServletRequest</span> <span class="variable">processedRequest</span> <span class="operator">=</span> request;</span><br><span class="line">		<span class="type">HandlerExecutionChain</span> <span class="variable">mappedHandler</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">		<span class="type">boolean</span> <span class="variable">multipartRequestParsed</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">		<span class="type">WebAsyncManager</span> <span class="variable">asyncManager</span> <span class="operator">=</span> WebAsyncUtils.getAsyncManager(request);</span><br><span class="line"></span><br><span class="line">		<span class="keyword">try</span> &#123;</span><br><span class="line">			<span class="type">ModelAndView</span> <span class="variable">mv</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">			<span class="type">Exception</span> <span class="variable">dispatchException</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line">            	...</span><br><span class="line">            </span><br><span class="line">                <span class="comment">//该方法内调用HandlerInterceptor的preHandle()</span></span><br><span class="line">				<span class="keyword">if</span> (!mappedHandler.applyPreHandle(processedRequest, response)) &#123;</span><br><span class="line">					<span class="keyword">return</span>;</span><br><span class="line">				&#125;</span><br><span class="line"></span><br><span class="line">				<span class="comment">// Actually invoke the handler.</span></span><br><span class="line">				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());</span><br><span class="line"></span><br><span class="line">            	...</span><br><span class="line">                <span class="comment">//该方法内调用HandlerInterceptor的postHandle()</span></span><br><span class="line">				mappedHandler.applyPostHandle(processedRequest, response, mv);</span><br><span class="line">			&#125;			</span><br><span class="line">        	processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">catch</span> (Exception ex) &#123;</span><br><span class="line">            <span class="comment">//该方法内调用HandlerInterceptor接口的afterCompletion方法</span></span><br><span class="line">			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">catch</span> (Throwable err) &#123;</span><br><span class="line">            <span class="comment">//该方法内调用HandlerInterceptor接口的afterCompletion方法</span></span><br><span class="line">			triggerAfterCompletion(processedRequest, response, mappedHandler,</span><br><span class="line">					<span class="keyword">new</span> <span class="title class_">NestedServletException</span>(<span class="string">&quot;Handler processing failed&quot;</span>, err));</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">finally</span> &#123;</span><br><span class="line">			...</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">triggerAfterCompletion</span><span class="params">(HttpServletRequest request, HttpServletResponse response,</span></span><br><span class="line"><span class="params">			<span class="meta">@Nullable</span> HandlerExecutionChain mappedHandler, Exception ex)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line"></span><br><span class="line">		<span class="keyword">if</span> (mappedHandler != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="comment">//该方法内调用HandlerInterceptor接口的afterCompletion方法</span></span><br><span class="line">			mappedHandler.triggerAfterCompletion(request, response, ex);</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">throw</span> ex;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">processDispatchResult</span><span class="params">(HttpServletRequest request, HttpServletResponse response,</span></span><br><span class="line"><span class="params">			<span class="meta">@Nullable</span> HandlerExecutionChain mappedHandler, <span class="meta">@Nullable</span> ModelAndView mv,</span></span><br><span class="line"><span class="params">			<span class="meta">@Nullable</span> Exception exception)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line"></span><br><span class="line">        ...</span><br><span class="line"></span><br><span class="line">		<span class="keyword">if</span> (mappedHandler != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="comment">//该方法内调用HandlerInterceptor接口的afterCompletion方法</span></span><br><span class="line">			<span class="comment">// Exception (if any) is already handled..</span></span><br><span class="line">			mappedHandler.triggerAfterCompletion(request, response, <span class="literal">null</span>);</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HandlerExecutionChain</span> &#123;</span><br><span class="line">    </span><br><span class="line">    ...</span><br><span class="line">    </span><br><span class="line">	<span class="type">boolean</span> <span class="title function_">applyPreHandle</span><span class="params">(HttpServletRequest request, HttpServletResponse response)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">		<span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="built_in">this</span>.interceptorList.size(); i++) &#123;</span><br><span class="line">			<span class="type">HandlerInterceptor</span> <span class="variable">interceptor</span> <span class="operator">=</span> <span class="built_in">this</span>.interceptorList.get(i);</span><br><span class="line">            <span class="comment">//HandlerInterceptor的preHandle方法</span></span><br><span class="line">			<span class="keyword">if</span> (!interceptor.preHandle(request, response, <span class="built_in">this</span>.handler)) &#123;</span><br><span class="line">                </span><br><span class="line">				triggerAfterCompletion(request, response, <span class="literal">null</span>);</span><br><span class="line">				<span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">			&#125;</span><br><span class="line">			<span class="built_in">this</span>.interceptorIndex = i;</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">   	<span class="keyword">void</span> <span class="title function_">applyPostHandle</span><span class="params">(HttpServletRequest request, HttpServletResponse response, <span class="meta">@Nullable</span> ModelAndView mv)</span></span><br><span class="line">			<span class="keyword">throws</span> Exception &#123;</span><br><span class="line"></span><br><span class="line">		<span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="built_in">this</span>.interceptorList.size() - <span class="number">1</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">			<span class="type">HandlerInterceptor</span> <span class="variable">interceptor</span> <span class="operator">=</span> <span class="built_in">this</span>.interceptorList.get(i);</span><br><span class="line">            </span><br><span class="line">            <span class="comment">//HandlerInterceptor接口的postHandle方法</span></span><br><span class="line">			interceptor.postHandle(request, response, <span class="built_in">this</span>.handler, mv);</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">triggerAfterCompletion</span><span class="params">(HttpServletRequest request, HttpServletResponse response, <span class="meta">@Nullable</span> Exception ex)</span> &#123;</span><br><span class="line">		<span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="built_in">this</span>.interceptorIndex; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">			<span class="type">HandlerInterceptor</span> <span class="variable">interceptor</span> <span class="operator">=</span> <span class="built_in">this</span>.interceptorList.get(i);</span><br><span class="line">			<span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">//HandlerInterceptor接口的afterCompletion方法</span></span><br><span class="line">				interceptor.afterCompletion(request, response, <span class="built_in">this</span>.handler, ex);</span><br><span class="line">			&#125;</span><br><span class="line">			<span class="keyword">catch</span> (Throwable ex2) &#123;</span><br><span class="line">				logger.error(<span class="string">&quot;HandlerInterceptor.afterCompletion threw exception&quot;</span>, ex2);</span><br><span class="line">			&#125;</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">    </span><br><span class="line">&#125; </span><br><span class="line">    </span><br></pre></td></tr></table></figure>



<h2 id="50、文件上传-单文件与多文件上传的使用"><a href="#50、文件上传-单文件与多文件上传的使用" class="headerlink" title="50、文件上传-单文件与多文件上传的使用"></a>50、文件上传-单文件与多文件上传的使用</h2><ul>
<li>页面代码<code>/static/form/form_layouts.html</code></li>
</ul>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">form</span> <span class="attr">role</span>=<span class="string">&quot;form&quot;</span> <span class="attr">th:action</span>=<span class="string">&quot;@&#123;/upload&#125;&quot;</span> <span class="attr">method</span>=<span class="string">&quot;post&quot;</span> <span class="attr">enctype</span>=<span class="string">&quot;multipart/form-data&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;form-group&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">label</span> <span class="attr">for</span>=<span class="string">&quot;exampleInputEmail1&quot;</span>&gt;</span>邮箱<span class="tag">&lt;/<span class="name">label</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;email&quot;</span> <span class="attr">name</span>=<span class="string">&quot;email&quot;</span> <span class="attr">class</span>=<span class="string">&quot;form-control&quot;</span> <span class="attr">id</span>=<span class="string">&quot;exampleInputEmail1&quot;</span> <span class="attr">placeholder</span>=<span class="string">&quot;Enter email&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;form-group&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">label</span> <span class="attr">for</span>=<span class="string">&quot;exampleInputPassword1&quot;</span>&gt;</span>名字<span class="tag">&lt;/<span class="name">label</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;text&quot;</span> <span class="attr">name</span>=<span class="string">&quot;username&quot;</span> <span class="attr">class</span>=<span class="string">&quot;form-control&quot;</span> <span class="attr">id</span>=<span class="string">&quot;exampleInputPassword1&quot;</span> <span class="attr">placeholder</span>=<span class="string">&quot;Password&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;form-group&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">label</span> <span class="attr">for</span>=<span class="string">&quot;exampleInputFile&quot;</span>&gt;</span>头像<span class="tag">&lt;/<span class="name">label</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;file&quot;</span> <span class="attr">name</span>=<span class="string">&quot;headerImg&quot;</span> <span class="attr">id</span>=<span class="string">&quot;exampleInputFile&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;form-group&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">label</span> <span class="attr">for</span>=<span class="string">&quot;exampleInputFile&quot;</span>&gt;</span>生活照<span class="tag">&lt;/<span class="name">label</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;file&quot;</span> <span class="attr">name</span>=<span class="string">&quot;photos&quot;</span> <span class="attr">multiple</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;checkbox&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">label</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;checkbox&quot;</span>&gt;</span> Check me out</span><br><span class="line">        <span class="tag">&lt;/<span class="name">label</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">button</span> <span class="attr">type</span>=<span class="string">&quot;submit&quot;</span> <span class="attr">class</span>=<span class="string">&quot;btn btn-primary&quot;</span>&gt;</span>提交<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">form</span>&gt;</span></span><br></pre></td></tr></table></figure>

<ul>
<li>控制层代码</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="meta">@Controller</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FormTestController</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/form_layouts&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">form_layouts</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;form/form_layouts&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@PostMapping(&quot;/upload&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">upload</span><span class="params">(<span class="meta">@RequestParam(&quot;email&quot;)</span> String email,</span></span><br><span class="line"><span class="params">                         <span class="meta">@RequestParam(&quot;username&quot;)</span> String username,</span></span><br><span class="line"><span class="params">                         <span class="meta">@RequestPart(&quot;headerImg&quot;)</span> MultipartFile headerImg,</span></span><br><span class="line"><span class="params">                         <span class="meta">@RequestPart(&quot;photos&quot;)</span> MultipartFile[] photos)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line"></span><br><span class="line">        log.info(<span class="string">&quot;上传的信息：email=&#123;&#125;，username=&#123;&#125;，headerImg=&#123;&#125;，photos=&#123;&#125;&quot;</span>,</span><br><span class="line">                 email,username,headerImg.getSize(),photos.length);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span>(!headerImg.isEmpty())&#123;</span><br><span class="line">            <span class="comment">//保存到文件服务器，OSS服务器</span></span><br><span class="line">            <span class="type">String</span> <span class="variable">originalFilename</span> <span class="operator">=</span> headerImg.getOriginalFilename();</span><br><span class="line">            headerImg.transferTo(<span class="keyword">new</span> <span class="title class_">File</span>(<span class="string">&quot;H:\\cache\\&quot;</span>+originalFilename));</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span>(photos.length &gt; <span class="number">0</span>)&#123;</span><br><span class="line">            <span class="keyword">for</span> (MultipartFile photo : photos) &#123;</span><br><span class="line">                <span class="keyword">if</span>(!photo.isEmpty())&#123;</span><br><span class="line">                    <span class="type">String</span> <span class="variable">originalFilename</span> <span class="operator">=</span> photo.getOriginalFilename();</span><br><span class="line">                    photo.transferTo(<span class="keyword">new</span> <span class="title class_">File</span>(<span class="string">&quot;H:\\cache\\&quot;</span>+originalFilename));</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;main&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>文件上传相关的配置类：</p>
<ul>
<li><code>org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration</code></li>
<li><code>org.springframework.boot.autoconfigure.web.servlet.MultipartProperties</code></li>
</ul>
<p>文件大小相关配置项：</p>
<figure class="highlight properties"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring.servlet.multipart.max-file-size</span>=<span class="string">10MB</span></span><br><span class="line"><span class="attr">spring.servlet.multipart.max-request-size</span>=<span class="string">100MB</span></span><br></pre></td></tr></table></figure>

<h2 id="51、文件上传-【源码流程】文件上传参数解析器"><a href="#51、文件上传-【源码流程】文件上传参数解析器" class="headerlink" title="51、文件上传-【源码流程】文件上传参数解析器"></a>51、文件上传-【源码流程】文件上传参数解析器</h2><p>文件上传相关的自动配置类<code>MultipartAutoConfiguration</code>有创建文件上传参数解析器<code>StandardServletMultipartResolver</code>。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration(proxyBeanMethods = false)</span></span><br><span class="line"><span class="meta">@ConditionalOnClass(&#123; Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class &#125;)</span></span><br><span class="line"><span class="meta">@ConditionalOnProperty(prefix = &quot;spring.servlet.multipart&quot;, name = &quot;enabled&quot;, matchIfMissing = true)</span></span><br><span class="line"><span class="meta">@ConditionalOnWebApplication(type = Type.SERVLET)</span></span><br><span class="line"><span class="meta">@EnableConfigurationProperties(MultipartProperties.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MultipartAutoConfiguration</span> &#123;</span><br><span class="line"></span><br><span class="line">	<span class="keyword">private</span> <span class="keyword">final</span> MultipartProperties multipartProperties;</span><br><span class="line"></span><br><span class="line">	<span class="keyword">public</span> <span class="title function_">MultipartAutoConfiguration</span><span class="params">(MultipartProperties multipartProperties)</span> &#123;</span><br><span class="line">		<span class="built_in">this</span>.multipartProperties = multipartProperties;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="meta">@Bean</span></span><br><span class="line">	<span class="meta">@ConditionalOnMissingBean(&#123; MultipartConfigElement.class, CommonsMultipartResolver.class &#125;)</span></span><br><span class="line">	<span class="keyword">public</span> MultipartConfigElement <span class="title function_">multipartConfigElement</span><span class="params">()</span> &#123;</span><br><span class="line">		<span class="keyword">return</span> <span class="built_in">this</span>.multipartProperties.createMultipartConfig();</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="meta">@Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)</span></span><br><span class="line">	<span class="meta">@ConditionalOnMissingBean(MultipartResolver.class)</span></span><br><span class="line">	<span class="keyword">public</span> StandardServletMultipartResolver <span class="title function_">multipartResolver</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">//配置好文件上传解析器</span></span><br><span class="line">		<span class="type">StandardServletMultipartResolver</span> <span class="variable">multipartResolver</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StandardServletMultipartResolver</span>();</span><br><span class="line">		multipartResolver.setResolveLazily(<span class="built_in">this</span>.multipartProperties.isResolveLazily());</span><br><span class="line">		<span class="keyword">return</span> multipartResolver;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//文件上传解析器</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">StandardServletMultipartResolver</span> <span class="keyword">implements</span> <span class="title class_">MultipartResolver</span> &#123;</span><br><span class="line"></span><br><span class="line">	<span class="keyword">private</span> <span class="type">boolean</span> <span class="variable">resolveLazily</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">	<span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setResolveLazily</span><span class="params">(<span class="type">boolean</span> resolveLazily)</span> &#123;</span><br><span class="line">		<span class="built_in">this</span>.resolveLazily = resolveLazily;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">	<span class="meta">@Override</span></span><br><span class="line">	<span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isMultipart</span><span class="params">(HttpServletRequest request)</span> &#123;</span><br><span class="line">		<span class="keyword">return</span> StringUtils.startsWithIgnoreCase(request.getContentType(), <span class="string">&quot;multipart/&quot;</span>);</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="meta">@Override</span></span><br><span class="line">	<span class="keyword">public</span> MultipartHttpServletRequest <span class="title function_">resolveMultipart</span><span class="params">(HttpServletRequest request)</span> <span class="keyword">throws</span> MultipartException &#123;</span><br><span class="line">		<span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">StandardMultipartHttpServletRequest</span>(request, <span class="built_in">this</span>.resolveLazily);</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="meta">@Override</span></span><br><span class="line">	<span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">cleanupMultipart</span><span class="params">(MultipartHttpServletRequest request)</span> &#123;</span><br><span class="line">		<span class="keyword">if</span> (!(request <span class="keyword">instanceof</span> AbstractMultipartHttpServletRequest) ||</span><br><span class="line">				((AbstractMultipartHttpServletRequest) request).isResolved()) &#123;</span><br><span class="line">			<span class="comment">// To be on the safe side: explicitly delete the parts,</span></span><br><span class="line">			<span class="comment">// but only actual file parts (for Resin compatibility)</span></span><br><span class="line">			<span class="keyword">try</span> &#123;</span><br><span class="line">				<span class="keyword">for</span> (Part part : request.getParts()) &#123;</span><br><span class="line">					<span class="keyword">if</span> (request.getFile(part.getName()) != <span class="literal">null</span>) &#123;</span><br><span class="line">						part.delete();</span><br><span class="line">					&#125;</span><br><span class="line">				&#125;</span><br><span class="line">			&#125;</span><br><span class="line">			<span class="keyword">catch</span> (Throwable ex) &#123;</span><br><span class="line">				LogFactory.getLog(getClass()).warn(<span class="string">&quot;Failed to perform cleanup of multipart items&quot;</span>, ex);</span><br><span class="line">			&#125;</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DispatcherServlet</span> <span class="keyword">extends</span> <span class="title class_">FrameworkServlet</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Nullable</span></span><br><span class="line">	<span class="keyword">private</span> MultipartResolver multipartResolver;</span><br><span class="line">    </span><br><span class="line">	<span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">initMultipartResolver</span><span class="params">(ApplicationContext context)</span> &#123;</span><br><span class="line">		...</span><br><span class="line">        </span><br><span class="line">        <span class="comment">//这个就是配置类配置的StandardServletMultipartResolver文件上传解析器</span></span><br><span class="line">		<span class="built_in">this</span>.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);</span><br><span class="line">		...</span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">	<span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">doDispatch</span><span class="params">(HttpServletRequest request, HttpServletResponse response)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">		<span class="type">HttpServletRequest</span> <span class="variable">processedRequest</span> <span class="operator">=</span> request;</span><br><span class="line">		<span class="type">HandlerExecutionChain</span> <span class="variable">mappedHandler</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">		<span class="type">boolean</span> <span class="variable">multipartRequestParsed</span> <span class="operator">=</span> <span class="literal">false</span>;<span class="comment">//最后finally的回收flag</span></span><br><span class="line">		...</span><br><span class="line">		<span class="keyword">try</span> &#123;</span><br><span class="line">			<span class="type">ModelAndView</span> <span class="variable">mv</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">			<span class="type">Exception</span> <span class="variable">dispatchException</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line">			<span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">//做预处理,如果有上传文件 就new StandardMultipartHttpServletRequest包装类</span></span><br><span class="line">				processedRequest = checkMultipart(request);</span><br><span class="line">				multipartRequestParsed = (processedRequest != request);</span><br><span class="line">				<span class="comment">// Determine handler for the current request.</span></span><br><span class="line">				mappedHandler = getHandler(processedRequest);</span><br><span class="line">				</span><br><span class="line">                ...</span><br><span class="line"></span><br><span class="line">				<span class="comment">// Determine handler adapter for the current request.</span></span><br><span class="line">				<span class="type">HandlerAdapter</span> <span class="variable">ha</span> <span class="operator">=</span> getHandlerAdapter(mappedHandler.getHandler());</span><br><span class="line"></span><br><span class="line">				...</span><br><span class="line"></span><br><span class="line">				<span class="comment">// Actually invoke the handler.</span></span><br><span class="line">				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());</span><br><span class="line">                </span><br><span class="line">            &#125;</span><br><span class="line">            ....</span><br><span class="line">            </span><br><span class="line">		<span class="keyword">finally</span> &#123;</span><br><span class="line"></span><br><span class="line">            ...</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (multipartRequestParsed) &#123;</span><br><span class="line">                cleanupMultipart(processedRequest);</span><br><span class="line">            &#125;</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="keyword">protected</span> HttpServletRequest <span class="title function_">checkMultipart</span><span class="params">(HttpServletRequest request)</span> <span class="keyword">throws</span> MultipartException &#123;</span><br><span class="line">		<span class="keyword">if</span> (<span class="built_in">this</span>.multipartResolver != <span class="literal">null</span> &amp;&amp; <span class="built_in">this</span>.multipartResolver.isMultipart(request)) &#123;</span><br><span class="line">            ...</span><br><span class="line">			<span class="keyword">return</span> <span class="built_in">this</span>.multipartResolver.resolveMultipart(request);</span><br><span class="line">            ...</span><br><span class="line">		&#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">	<span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">cleanupMultipart</span><span class="params">(HttpServletRequest request)</span> &#123;</span><br><span class="line">		<span class="keyword">if</span> (<span class="built_in">this</span>.multipartResolver != <span class="literal">null</span>) &#123;</span><br><span class="line">			<span class="type">MultipartHttpServletRequest</span> <span class="variable">multipartRequest</span> <span class="operator">=</span></span><br><span class="line">					WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);</span><br><span class="line">			<span class="keyword">if</span> (multipartRequest != <span class="literal">null</span>) &#123;</span><br><span class="line">				<span class="built_in">this</span>.multipartResolver.cleanupMultipart(multipartRequest);</span><br><span class="line">			&#125;</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><code>mv = ha.handle(processedRequest, response, mappedHandler.getHandler());</code>跳到以下的类</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RequestMappingHandlerAdapter</span> <span class="keyword">extends</span> <span class="title class_">AbstractHandlerMethodAdapter</span></span><br><span class="line">		<span class="keyword">implements</span> <span class="title class_">BeanFactoryAware</span>, InitializingBean &#123;</span><br><span class="line">	<span class="meta">@Override</span></span><br><span class="line">	<span class="keyword">protected</span> ModelAndView <span class="title function_">handleInternal</span><span class="params">(HttpServletRequest request,</span></span><br><span class="line"><span class="params">			HttpServletResponse response, HandlerMethod handlerMethod)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">		ModelAndView mav;</span><br><span class="line">		...</span><br><span class="line">		mav = invokeHandlerMethod(request, response, handlerMethod);</span><br><span class="line">        ...</span><br><span class="line">		<span class="keyword">return</span> mav;</span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Nullable</span></span><br><span class="line">	<span class="keyword">protected</span> ModelAndView <span class="title function_">invokeHandlerMethod</span><span class="params">(HttpServletRequest request,</span></span><br><span class="line"><span class="params">			HttpServletResponse response, HandlerMethod handlerMethod)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line"></span><br><span class="line">		<span class="type">ServletWebRequest</span> <span class="variable">webRequest</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServletWebRequest</span>(request, response);</span><br><span class="line">		<span class="keyword">try</span> &#123;</span><br><span class="line">			<span class="type">WebDataBinderFactory</span> <span class="variable">binderFactory</span> <span class="operator">=</span> getDataBinderFactory(handlerMethod);</span><br><span class="line">			<span class="type">ModelFactory</span> <span class="variable">modelFactory</span> <span class="operator">=</span> getModelFactory(handlerMethod, binderFactory);</span><br><span class="line"></span><br><span class="line">			<span class="type">ServletInvocableHandlerMethod</span> <span class="variable">invocableMethod</span> <span class="operator">=</span> createInvocableHandlerMethod(handlerMethod);</span><br><span class="line">			<span class="keyword">if</span> (<span class="built_in">this</span>.argumentResolvers != <span class="literal">null</span>) &#123;<span class="comment">//关注点</span></span><br><span class="line">				invocableMethod.setHandlerMethodArgumentResolvers(<span class="built_in">this</span>.argumentResolvers);</span><br><span class="line">			&#125;</span><br><span class="line">			...</span><br><span class="line">			invocableMethod.invokeAndHandle(webRequest, mavContainer);</span><br><span class="line">			...</span><br><span class="line"></span><br><span class="line">			<span class="keyword">return</span> getModelAndView(mavContainer, modelFactory, webRequest);</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">finally</span> &#123;</span><br><span class="line">			webRequest.requestCompleted();</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><code>this.argumentResolvers</code>其中主角类<code>RequestPartMethodArgumentResolver</code>用来生成</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ServletInvocableHandlerMethod</span> <span class="keyword">extends</span> <span class="title class_">InvocableHandlerMethod</span> &#123;</span><br><span class="line">    </span><br><span class="line">    ...</span><br><span class="line">	<span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">invokeAndHandle</span><span class="params">(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,</span></span><br><span class="line"><span class="params">			Object... providedArgs)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">		<span class="type">Object</span> <span class="variable">returnValue</span> <span class="operator">=</span> invokeForRequest(webRequest, mavContainer, providedArgs);</span><br><span class="line">		...</span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">	<span class="meta">@Nullable</span></span><br><span class="line">	<span class="keyword">public</span> Object <span class="title function_">invokeForRequest</span><span class="params">(NativeWebRequest request, <span class="meta">@Nullable</span> ModelAndViewContainer mavContainer,</span></span><br><span class="line"><span class="params">			Object... providedArgs)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line"></span><br><span class="line">		Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);</span><br><span class="line">		...</span><br><span class="line">		<span class="keyword">return</span> doInvoke(args);<span class="comment">//反射调用</span></span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">	<span class="meta">@Nullable</span></span><br><span class="line">	<span class="keyword">protected</span> Object <span class="title function_">doInvoke</span><span class="params">(Object... args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">		<span class="type">Method</span> <span class="variable">method</span> <span class="operator">=</span> getBridgedMethod();</span><br><span class="line">		ReflectionUtils.makeAccessible(method);</span><br><span class="line">		<span class="keyword">return</span> method.invoke(getBean(), args);</span><br><span class="line">		...</span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//处理得出multipart参数，准备稍后的反射调用（@PostMapping标记的上传方法）</span></span><br><span class="line">    <span class="keyword">protected</span> Object[] getMethodArgumentValues(NativeWebRequest request, <span class="meta">@Nullable</span> ModelAndViewContainer mavContainer,</span><br><span class="line">			Object... providedArgs) <span class="keyword">throws</span> Exception &#123;</span><br><span class="line"></span><br><span class="line">		MethodParameter[] parameters = getMethodParameters();</span><br><span class="line">		...</span><br><span class="line">		Object[] args = <span class="keyword">new</span> <span class="title class_">Object</span>[parameters.length];</span><br><span class="line">		<span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; parameters.length; i++) &#123;</span><br><span class="line">			<span class="type">MethodParameter</span> <span class="variable">parameter</span> <span class="operator">=</span> parameters[i];</span><br><span class="line">			parameter.initParameterNameDiscovery(<span class="built_in">this</span>.parameterNameDiscoverer);</span><br><span class="line">			args[i] = findProvidedArgument(parameter, providedArgs);</span><br><span class="line">			<span class="keyword">if</span> (args[i] != <span class="literal">null</span>) &#123;</span><br><span class="line">				<span class="keyword">continue</span>;</span><br><span class="line">			&#125;</span><br><span class="line">            <span class="comment">//关注点1</span></span><br><span class="line">			<span class="keyword">if</span> (!<span class="built_in">this</span>.resolvers.supportsParameter(parameter)) &#123;</span><br><span class="line">				<span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(formatArgumentError(parameter, <span class="string">&quot;No suitable resolver&quot;</span>));</span><br><span class="line">			&#125;</span><br><span class="line">			<span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">//关注点2</span></span><br><span class="line">				args[i] = <span class="built_in">this</span>.resolvers.resolveArgument(parameter, mavContainer, request, <span class="built_in">this</span>.dataBinderFactory);</span><br><span class="line">			&#125;</span><br><span class="line">			<span class="keyword">catch</span> (Exception ex) &#123;</span><br><span class="line">				...</span><br><span class="line">			&#125;</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">return</span> args;</span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RequestPartMethodArgumentResolver</span> <span class="keyword">extends</span> <span class="title class_">AbstractMessageConverterMethodArgumentResolver</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//对应上面代码关注点1</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">	<span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">supportsParameter</span><span class="params">(MethodParameter parameter)</span> &#123;</span><br><span class="line">        <span class="comment">//标注@RequestPart的参数</span></span><br><span class="line">		<span class="keyword">if</span> (parameter.hasParameterAnnotation(RequestPart.class)) &#123;</span><br><span class="line">			<span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">else</span> &#123;</span><br><span class="line">			<span class="keyword">if</span> (parameter.hasParameterAnnotation(RequestParam.class)) &#123;</span><br><span class="line">				<span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">			&#125;</span><br><span class="line">			<span class="keyword">return</span> MultipartResolutionDelegate.isMultipartArgument(parameter.nestedIfOptional());</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//对应上面代码关注点2</span></span><br><span class="line">	<span class="meta">@Override</span></span><br><span class="line">	<span class="meta">@Nullable</span></span><br><span class="line">	<span class="keyword">public</span> Object <span class="title function_">resolveArgument</span><span class="params">(MethodParameter parameter, <span class="meta">@Nullable</span> ModelAndViewContainer mavContainer,</span></span><br><span class="line"><span class="params">			NativeWebRequest request, <span class="meta">@Nullable</span> WebDataBinderFactory binderFactory)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line"></span><br><span class="line">		<span class="type">HttpServletRequest</span> <span class="variable">servletRequest</span> <span class="operator">=</span> request.getNativeRequest(HttpServletRequest.class);</span><br><span class="line">		Assert.state(servletRequest != <span class="literal">null</span>, <span class="string">&quot;No HttpServletRequest&quot;</span>);</span><br><span class="line"></span><br><span class="line">		<span class="type">RequestPart</span> <span class="variable">requestPart</span> <span class="operator">=</span> parameter.getParameterAnnotation(RequestPart.class);</span><br><span class="line">		<span class="type">boolean</span> <span class="variable">isRequired</span> <span class="operator">=</span> ((requestPart == <span class="literal">null</span> || requestPart.required()) &amp;&amp; !parameter.isOptional());</span><br><span class="line"></span><br><span class="line">		<span class="type">String</span> <span class="variable">name</span> <span class="operator">=</span> getPartName(parameter, requestPart);</span><br><span class="line">		parameter = parameter.nestedIfOptional();</span><br><span class="line">		<span class="type">Object</span> <span class="variable">arg</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//封装成MultipartFile类型的对象作参数</span></span><br><span class="line">		<span class="type">Object</span> <span class="variable">mpArg</span> <span class="operator">=</span> MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);</span><br><span class="line">		<span class="keyword">if</span> (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) &#123;</span><br><span class="line">			arg = mpArg;</span><br><span class="line">		&#125;</span><br><span class="line">        </span><br><span class="line">        ...</span><br><span class="line"></span><br><span class="line">		<span class="keyword">return</span> adaptArgumentIfNecessary(arg, parameter);</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">MultipartResolutionDelegate</span> &#123;</span><br><span class="line">    ...</span><br><span class="line">    </span><br><span class="line">	<span class="meta">@Nullable</span></span><br><span class="line">	<span class="keyword">public</span> <span class="keyword">static</span> Object <span class="title function_">resolveMultipartArgument</span><span class="params">(String name, MethodParameter parameter, HttpServletRequest request)</span></span><br><span class="line">			<span class="keyword">throws</span> Exception &#123;</span><br><span class="line"></span><br><span class="line">		<span class="type">MultipartHttpServletRequest</span> <span class="variable">multipartRequest</span> <span class="operator">=</span></span><br><span class="line">				WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);</span><br><span class="line">		<span class="type">boolean</span> <span class="variable">isMultipart</span> <span class="operator">=</span> (multipartRequest != <span class="literal">null</span> || isMultipartContent(request));</span><br><span class="line"></span><br><span class="line">		<span class="keyword">if</span> (MultipartFile.class == parameter.getNestedParameterType()) &#123;</span><br><span class="line">			<span class="keyword">if</span> (!isMultipart) &#123;</span><br><span class="line">				<span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">			&#125;</span><br><span class="line">			<span class="keyword">if</span> (multipartRequest == <span class="literal">null</span>) &#123;</span><br><span class="line">				multipartRequest = <span class="keyword">new</span> <span class="title class_">StandardMultipartHttpServletRequest</span>(request);</span><br><span class="line">			&#125;</span><br><span class="line">			<span class="keyword">return</span> multipartRequest.getFile(name);</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">else</span> <span class="keyword">if</span> (isMultipartFileCollection(parameter)) &#123;</span><br><span class="line">			<span class="keyword">if</span> (!isMultipart) &#123;</span><br><span class="line">				<span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">			&#125;</span><br><span class="line">			<span class="keyword">if</span> (multipartRequest == <span class="literal">null</span>) &#123;</span><br><span class="line">				multipartRequest = <span class="keyword">new</span> <span class="title class_">StandardMultipartHttpServletRequest</span>(request);</span><br><span class="line">			&#125;</span><br><span class="line">			List&lt;MultipartFile&gt; files = multipartRequest.getFiles(name);</span><br><span class="line">			<span class="keyword">return</span> (!files.isEmpty() ? files : <span class="literal">null</span>);</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">else</span> <span class="keyword">if</span> (isMultipartFileArray(parameter)) &#123;</span><br><span class="line">			<span class="keyword">if</span> (!isMultipart) &#123;</span><br><span class="line">				<span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">			&#125;</span><br><span class="line">			<span class="keyword">if</span> (multipartRequest == <span class="literal">null</span>) &#123;</span><br><span class="line">				multipartRequest = <span class="keyword">new</span> <span class="title class_">StandardMultipartHttpServletRequest</span>(request);</span><br><span class="line">			&#125;</span><br><span class="line">			List&lt;MultipartFile&gt; files = multipartRequest.getFiles(name);</span><br><span class="line">			<span class="keyword">return</span> (!files.isEmpty() ? files.toArray(<span class="keyword">new</span> <span class="title class_">MultipartFile</span>[<span class="number">0</span>]) : <span class="literal">null</span>);</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">else</span> <span class="keyword">if</span> (Part.class == parameter.getNestedParameterType()) &#123;</span><br><span class="line">			<span class="keyword">if</span> (!isMultipart) &#123;</span><br><span class="line">				<span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">			&#125;</span><br><span class="line">			<span class="keyword">return</span> request.getPart(name);</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">else</span> <span class="keyword">if</span> (isPartCollection(parameter)) &#123;</span><br><span class="line">			<span class="keyword">if</span> (!isMultipart) &#123;</span><br><span class="line">				<span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">			&#125;</span><br><span class="line">			List&lt;Part&gt; parts = resolvePartList(request, name);</span><br><span class="line">			<span class="keyword">return</span> (!parts.isEmpty() ? parts : <span class="literal">null</span>);</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">else</span> <span class="keyword">if</span> (isPartArray(parameter)) &#123;</span><br><span class="line">			<span class="keyword">if</span> (!isMultipart) &#123;</span><br><span class="line">				<span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">			&#125;</span><br><span class="line">			List&lt;Part&gt; parts = resolvePartList(request, name);</span><br><span class="line">			<span class="keyword">return</span> (!parts.isEmpty() ? parts.toArray(<span class="keyword">new</span> <span class="title class_">Part</span>[<span class="number">0</span>]) : <span class="literal">null</span>);</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">else</span> &#123;</span><br><span class="line">			<span class="keyword">return</span> UNRESOLVABLE;</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">    ...</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h2 id="52、错误处理-SpringBoot默认错误处理机制"><a href="#52、错误处理-SpringBoot默认错误处理机制" class="headerlink" title="52、错误处理-SpringBoot默认错误处理机制"></a>52、错误处理-SpringBoot默认错误处理机制</h2><p><a target="_blank" rel="noopener" href="https://docs.spring.io/spring-boot/docs/2.4.2/reference/htmlsingle/#boot-features-error-handling">Spring Boot官方文档 - Error Handling</a></p>
<p><strong>默认规则</strong>：</p>
<ul>
<li><p>默认情况下，Spring Boot提供<code>/error</code>处理所有错误的映射</p>
</li>
<li><p>机器客户端，它将生成JSON响应，其中包含错误，HTTP状态和异常消息的详细信息。对于浏览器客户端，响应一个“ whitelabel”错误视图，以HTML格式呈现相同的数据</p>
</li>
</ul>
<figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;timestamp&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2020-11-22T05:53:28.416+00:00&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="number">404</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;error&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Not Found&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;No message available&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/asadada&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>

<ul>
<li><p>要对其进行自定义，添加<code>View</code>解析为<code>error</code></p>
</li>
<li><p>要完全替换默认行为，可以实现 <code>ErrorController </code>并注册该类型的Bean定义，或添加<code>ErrorAttributes类型的组件</code>以使用现有机制但替换其内容。</p>
</li>
<li><p><code>/templates/error/</code>下的4xx，5xx页面会被自动解析</p>
</li>
</ul>
<h2 id="53、错误处理-【源码分析】底层组件功能分析"><a href="#53、错误处理-【源码分析】底层组件功能分析" class="headerlink" title="53、错误处理-【源码分析】底层组件功能分析"></a>53、错误处理-【源码分析】底层组件功能分析</h2><ul>
<li><code>ErrorMvcAutoConfiguration</code>  自动配置异常处理规则</li>
<li><strong>容器中的组件</strong>：类型：<code>DefaultErrorAttributes</code> -&gt; id：<code>errorAttributes</code></li>
<li><code>public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver</code><ul>
<li><code>DefaultErrorAttributes</code>：定义错误页面中可以包含数据（异常明细，堆栈信息等）。</li>
</ul>
</li>
<li><strong>容器中的组件</strong>：类型：<code>BasicErrorController</code> –&gt; id：<code>basicErrorController</code>（json+白页 适配响应）</li>
<li><strong>处理默认 <code>/error</code> 路径的请求</strong>，页面响应 <code>new ModelAndView(&quot;error&quot;, model);</code><ul>
<li>容器中有组件 <code>View</code>-&gt;id是error；（响应默认错误页）</li>
<li>容器中放组件 <code>BeanNameViewResolver</code>（视图解析器）；按照返回的视图名作为组件的id去容器中找<code>View</code>对象。</li>
</ul>
</li>
<li><strong>容器中的组件</strong>：类型：<code>DefaultErrorViewResolver</code> -&gt; id：<code>conventionErrorViewResolver</code></li>
<li><strong>如果发生异常错误，会以HTTP的状态码 作为视图页地址（viewName），找到真正的页面</strong>（主要作用）。<ul>
<li>error&#x2F;404、5xx.html</li>
<li>如果想要返回页面，就会找error视图（<code>StaticView</code>默认是一个白页）。</li>
</ul>
</li>
</ul>
<h2 id="54、错误处理-【源码流程】异常处理流程"><a href="#54、错误处理-【源码流程】异常处理流程" class="headerlink" title="54、错误处理-【源码流程】异常处理流程"></a>54、错误处理-【源码流程】异常处理流程</h2><p>譬如写一个会抛出异常的控制层：</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HelloController</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@RequestMapping(&quot;/hello&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">handle01</span><span class="params">()</span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span> / <span class="number">0</span>;<span class="comment">//将会抛出ArithmeticException</span></span><br><span class="line"></span><br><span class="line">        log.info(<span class="string">&quot;Hello, Spring Boot 2!&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;Hello, Spring Boot 2!&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>当浏览器发出<code>/hello</code>请求，<code>DispatcherServlet</code>的<code>doDispatch()</code>的<code>mv = ha.handle(processedRequest, response, mappedHandler.getHandler());</code>将会抛出<code>ArithmeticException</code>。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DispatcherServlet</span> <span class="keyword">extends</span> <span class="title class_">FrameworkServlet</span> &#123;</span><br><span class="line">    ...</span><br><span class="line">	<span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">doDispatch</span><span class="params">(HttpServletRequest request, HttpServletResponse response)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">		...</span><br><span class="line">				<span class="comment">// Actually invoke the handler.</span></span><br><span class="line">            	<span class="comment">//将会抛出ArithmeticException</span></span><br><span class="line">				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());</span><br><span class="line"></span><br><span class="line">				applyDefaultViewName(processedRequest, mv);</span><br><span class="line">				mappedHandler.applyPostHandle(processedRequest, response, mv);</span><br><span class="line">			&#125;</span><br><span class="line">			<span class="keyword">catch</span> (Exception ex) &#123;</span><br><span class="line">                <span class="comment">//将会捕捉ArithmeticException</span></span><br><span class="line">				dispatchException = ex;</span><br><span class="line">			&#125;</span><br><span class="line">			<span class="keyword">catch</span> (Throwable err) &#123;</span><br><span class="line">				...</span><br><span class="line">			&#125;</span><br><span class="line">    		<span class="comment">//捕捉后，继续运行</span></span><br><span class="line">			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">catch</span> (Exception ex) &#123;</span><br><span class="line">			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">catch</span> (Throwable err) &#123;</span><br><span class="line">			triggerAfterCompletion(processedRequest, response, mappedHandler,</span><br><span class="line">					<span class="keyword">new</span> <span class="title class_">NestedServletException</span>(<span class="string">&quot;Handler processing failed&quot;</span>, err));</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">finally</span> &#123;</span><br><span class="line">			...</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">processDispatchResult</span><span class="params">(HttpServletRequest request, HttpServletResponse response,</span></span><br><span class="line"><span class="params">			<span class="meta">@Nullable</span> HandlerExecutionChain mappedHandler, <span class="meta">@Nullable</span> ModelAndView mv,</span></span><br><span class="line"><span class="params">			<span class="meta">@Nullable</span> Exception exception)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line"></span><br><span class="line">		<span class="type">boolean</span> <span class="variable">errorView</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">		<span class="keyword">if</span> (exception != <span class="literal">null</span>) &#123;</span><br><span class="line">			<span class="keyword">if</span> (exception <span class="keyword">instanceof</span> ModelAndViewDefiningException) &#123;</span><br><span class="line">				...</span><br><span class="line">			&#125;</span><br><span class="line">			<span class="keyword">else</span> &#123;</span><br><span class="line">				<span class="type">Object</span> <span class="variable">handler</span> <span class="operator">=</span> (mappedHandler != <span class="literal">null</span> ? mappedHandler.getHandler() : <span class="literal">null</span>);</span><br><span class="line">				<span class="comment">//ArithmeticException将在这处理</span></span><br><span class="line">                mv = processHandlerException(request, response, handler, exception);</span><br><span class="line">				errorView = (mv != <span class="literal">null</span>);</span><br><span class="line">			&#125;</span><br><span class="line">		&#125;</span><br><span class="line">		...</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="keyword">protected</span> ModelAndView <span class="title function_">processHandlerException</span><span class="params">(HttpServletRequest request, HttpServletResponse response,</span></span><br><span class="line"><span class="params">			<span class="meta">@Nullable</span> Object handler, Exception ex)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line"></span><br><span class="line">		<span class="comment">// Success and error responses may use different content types</span></span><br><span class="line">		request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);</span><br><span class="line"></span><br><span class="line">		<span class="comment">// Check registered HandlerExceptionResolvers...</span></span><br><span class="line">		<span class="type">ModelAndView</span> <span class="variable">exMv</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">		<span class="keyword">if</span> (<span class="built_in">this</span>.handlerExceptionResolvers != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="comment">//遍历所有的 handlerExceptionResolvers，看谁能处理当前异常HandlerExceptionResolver处理器异常解析器</span></span><br><span class="line">			<span class="keyword">for</span> (HandlerExceptionResolver resolver : <span class="built_in">this</span>.handlerExceptionResolvers) &#123;</span><br><span class="line">				exMv = resolver.resolveException(request, response, handler, ex);</span><br><span class="line">				<span class="keyword">if</span> (exMv != <span class="literal">null</span>) &#123;</span><br><span class="line">					<span class="keyword">break</span>;</span><br><span class="line">				&#125;</span><br><span class="line">			&#125;</span><br><span class="line">		&#125;</span><br><span class="line">		...</span><br><span class="line">	</span><br><span class="line">        <span class="comment">//若只有系统的自带的异常解析器（没有自定义的），异常还是会抛出</span></span><br><span class="line">		<span class="keyword">throw</span> ex;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>系统自带的<strong>异常解析器</strong>：</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Spring/20210205011338251.png" alt="在这里插入图片描述"></p>
<ul>
<li><code>DefaultErrorAttributes</code>先来处理异常，它主要功能把异常信息保存到request域，并且返回null。</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DefaultErrorAttributes</span> <span class="keyword">implements</span> <span class="title class_">ErrorAttributes</span>, HandlerExceptionResolver, Ordered &#123;</span><br><span class="line">    ...</span><br><span class="line">    <span class="keyword">public</span> ModelAndView <span class="title function_">resolveException</span><span class="params">(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.storeErrorAttributes(request, ex);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">storeErrorAttributes</span><span class="params">(HttpServletRequest request, Exception ex)</span> &#123;</span><br><span class="line">        request.setAttribute(ERROR_ATTRIBUTE, ex);<span class="comment">//把异常信息保存到request域</span></span><br><span class="line">    &#125;</span><br><span class="line">    ...</span><br><span class="line">    </span><br><span class="line">&#125;    </span><br></pre></td></tr></table></figure>

<ul>
<li><p>默认没有任何解析器（上图的<code>HandlerExceptionResolverComposite</code>）能处理异常，所以最后异常会被抛出。</p>
</li>
<li><p>最终底层就会转发<code>/error</code> 请求。会被底层的<code>BasicErrorController</code>处理。</p>
</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Controller</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;$&#123;server.error.path:$&#123;error.path:/error&#125;&#125;&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BasicErrorController</span> <span class="keyword">extends</span> <span class="title class_">AbstractErrorController</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)</span></span><br><span class="line">    <span class="keyword">public</span> ModelAndView <span class="title function_">errorHtml</span><span class="params">(HttpServletRequest request, HttpServletResponse response)</span> &#123;</span><br><span class="line">       <span class="type">HttpStatus</span> <span class="variable">status</span> <span class="operator">=</span> getStatus(request);</span><br><span class="line">       Map&lt;String, Object&gt; model = Collections</span><br><span class="line">             .unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));</span><br><span class="line">       response.setStatus(status.value());</span><br><span class="line">       <span class="type">ModelAndView</span> <span class="variable">modelAndView</span> <span class="operator">=</span> resolveErrorView(request, response, status, model);</span><br><span class="line">       <span class="comment">//如果/template/error内没有4**.html或5**.html，</span></span><br><span class="line">       <span class="comment">//modelAndView为空，最终还是返回viewName为error的modelAndView</span></span><br><span class="line">       <span class="keyword">return</span> (modelAndView != <span class="literal">null</span>) ? modelAndView : <span class="keyword">new</span> <span class="title class_">ModelAndView</span>(<span class="string">&quot;error&quot;</span>, model);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">doDispatch</span><span class="params">(HttpServletRequest request, HttpServletResponse response)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">    </span><br><span class="line">    ...</span><br><span class="line">    </span><br><span class="line">	<span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">doDispatch</span><span class="params">(HttpServletRequest request, HttpServletResponse response)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        ...</span><br><span class="line">     	<span class="comment">// Actually invoke the handler.</span></span><br><span class="line">		mv = ha.handle(processedRequest, response, mappedHandler.getHandler());</span><br><span class="line">		...</span><br><span class="line">        <span class="comment">//渲染页面</span></span><br><span class="line">		processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);</span><br><span class="line">        ...</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">processDispatchResult</span><span class="params">(HttpServletRequest request, HttpServletResponse response,</span></span><br><span class="line"><span class="params">			<span class="meta">@Nullable</span> HandlerExecutionChain mappedHandler, <span class="meta">@Nullable</span> ModelAndView mv,</span></span><br><span class="line"><span class="params">			<span class="meta">@Nullable</span> Exception exception)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line"></span><br><span class="line">        <span class="type">boolean</span> <span class="variable">errorView</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">        ...</span><br><span class="line">		<span class="comment">// Did the handler return a view to render?</span></span><br><span class="line">		<span class="keyword">if</span> (mv != <span class="literal">null</span> &amp;&amp; !mv.wasCleared()) &#123;</span><br><span class="line">			render(mv, request, response);</span><br><span class="line">			<span class="keyword">if</span> (errorView) &#123;</span><br><span class="line">				WebUtils.clearErrorRequestAttributes(request);</span><br><span class="line">			&#125;</span><br><span class="line">		&#125;</span><br><span class="line">		...</span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">render</span><span class="params">(ModelAndView mv, HttpServletRequest request, HttpServletResponse response)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">		...</span><br><span class="line"></span><br><span class="line">		View view;</span><br><span class="line">		<span class="type">String</span> <span class="variable">viewName</span> <span class="operator">=</span> mv.getViewName();</span><br><span class="line">		<span class="keyword">if</span> (viewName != <span class="literal">null</span>) &#123;</span><br><span class="line">			<span class="comment">// We need to resolve the view name.</span></span><br><span class="line">            <span class="comment">//找出合适error的View，如果/template/error内没有4**.html或5**.html，</span></span><br><span class="line">            <span class="comment">//将会返回默认异常页面ErrorMvcAutoConfiguration.StaticView</span></span><br><span class="line">            <span class="comment">//这里按需深究代码吧！</span></span><br><span class="line">			view = resolveViewName(viewName, mv.getModelInternal(), locale, request);</span><br><span class="line">			...</span><br><span class="line">		&#125;</span><br><span class="line">		...</span><br><span class="line">		<span class="keyword">try</span> &#123;</span><br><span class="line">			<span class="keyword">if</span> (mv.getStatus() != <span class="literal">null</span>) &#123;</span><br><span class="line">				response.setStatus(mv.getStatus().value());</span><br><span class="line">			&#125;</span><br><span class="line">            <span class="comment">//看下面代码块的StaticView的render块</span></span><br><span class="line">			view.render(mv.getModelInternal(), request, response);</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">catch</span> (Exception ex) &#123;</span><br><span class="line">			...</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration(proxyBeanMethods = false)</span></span><br><span class="line"><span class="meta">@ConditionalOnWebApplication(type = Type.SERVLET)</span></span><br><span class="line"><span class="meta">@ConditionalOnClass(&#123; Servlet.class, DispatcherServlet.class &#125;)</span></span><br><span class="line"><span class="comment">// Load before the main WebMvcAutoConfiguration so that the error View is available</span></span><br><span class="line"><span class="meta">@AutoConfigureBefore(WebMvcAutoConfiguration.class)</span></span><br><span class="line"><span class="meta">@EnableConfigurationProperties(&#123; ServerProperties.class, ResourceProperties.class, WebMvcProperties.class &#125;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ErrorMvcAutoConfiguration</span> &#123;</span><br><span class="line">    </span><br><span class="line">    ...</span><br><span class="line">        </span><br><span class="line">   	<span class="meta">@Configuration(proxyBeanMethods = false)</span></span><br><span class="line">	<span class="meta">@ConditionalOnProperty(prefix = &quot;server.error.whitelabel&quot;, name = &quot;enabled&quot;, matchIfMissing = true)</span></span><br><span class="line">	<span class="meta">@Conditional(ErrorTemplateMissingCondition.class)</span></span><br><span class="line">	<span class="keyword">protected</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">WhitelabelErrorViewConfiguration</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//将创建一个名为error的系统默认异常页面View的Bean</span></span><br><span class="line">		<span class="keyword">private</span> <span class="keyword">final</span> <span class="type">StaticView</span> <span class="variable">defaultErrorView</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StaticView</span>();</span><br><span class="line"></span><br><span class="line">		<span class="meta">@Bean(name = &quot;error&quot;)</span></span><br><span class="line">		<span class="meta">@ConditionalOnMissingBean(name = &quot;error&quot;)</span></span><br><span class="line">		<span class="keyword">public</span> View <span class="title function_">defaultErrorView</span><span class="params">()</span> &#123;</span><br><span class="line">			<span class="keyword">return</span> <span class="built_in">this</span>.defaultErrorView;</span><br><span class="line">		&#125;</span><br><span class="line"></span><br><span class="line">		<span class="comment">// If the user adds @EnableWebMvc then the bean name view resolver from</span></span><br><span class="line">		<span class="comment">// WebMvcAutoConfiguration disappears, so add it back in to avoid disappointment.</span></span><br><span class="line">		<span class="meta">@Bean</span></span><br><span class="line">		<span class="meta">@ConditionalOnMissingBean</span></span><br><span class="line">		<span class="keyword">public</span> BeanNameViewResolver <span class="title function_">beanNameViewResolver</span><span class="params">()</span> &#123;</span><br><span class="line">			<span class="type">BeanNameViewResolver</span> <span class="variable">resolver</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BeanNameViewResolver</span>();</span><br><span class="line">			resolver.setOrder(Ordered.LOWEST_PRECEDENCE - <span class="number">10</span>);</span><br><span class="line">			<span class="keyword">return</span> resolver;</span><br><span class="line">		&#125;</span><br><span class="line"></span><br><span class="line">	&#125;     </span><br><span class="line">   </span><br><span class="line">    </span><br><span class="line">	<span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">StaticView</span> <span class="keyword">implements</span> <span class="title class_">View</span> &#123;</span><br><span class="line"></span><br><span class="line">		<span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">MediaType</span> <span class="variable">TEXT_HTML_UTF8</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MediaType</span>(<span class="string">&quot;text&quot;</span>, <span class="string">&quot;html&quot;</span>, StandardCharsets.UTF_8);</span><br><span class="line"></span><br><span class="line">		<span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Log</span> <span class="variable">logger</span> <span class="operator">=</span> LogFactory.getLog(StaticView.class);</span><br><span class="line"></span><br><span class="line">		<span class="meta">@Override</span></span><br><span class="line">		<span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">render</span><span class="params">(Map&lt;String, ?&gt; model, HttpServletRequest request, HttpServletResponse response)</span></span><br><span class="line">				<span class="keyword">throws</span> Exception &#123;</span><br><span class="line">			<span class="keyword">if</span> (response.isCommitted()) &#123;</span><br><span class="line">				<span class="type">String</span> <span class="variable">message</span> <span class="operator">=</span> getMessage(model);</span><br><span class="line">				logger.error(message);</span><br><span class="line">				<span class="keyword">return</span>;</span><br><span class="line">			&#125;</span><br><span class="line">			response.setContentType(TEXT_HTML_UTF8.toString());</span><br><span class="line">			<span class="type">StringBuilder</span> <span class="variable">builder</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>();</span><br><span class="line">			<span class="type">Object</span> <span class="variable">timestamp</span> <span class="operator">=</span> model.get(<span class="string">&quot;timestamp&quot;</span>);</span><br><span class="line">			<span class="type">Object</span> <span class="variable">message</span> <span class="operator">=</span> model.get(<span class="string">&quot;message&quot;</span>);</span><br><span class="line">			<span class="type">Object</span> <span class="variable">trace</span> <span class="operator">=</span> model.get(<span class="string">&quot;trace&quot;</span>);</span><br><span class="line">			<span class="keyword">if</span> (response.getContentType() == <span class="literal">null</span>) &#123;</span><br><span class="line">				response.setContentType(getContentType());</span><br><span class="line">			&#125;</span><br><span class="line">            <span class="comment">//系统默认异常页面html代码</span></span><br><span class="line">			builder.append(<span class="string">&quot;&lt;html&gt;&lt;body&gt;&lt;h1&gt;Whitelabel Error Page&lt;/h1&gt;&quot;</span>).append(</span><br><span class="line">					<span class="string">&quot;&lt;p&gt;This application has no explicit mapping for /error, so you are seeing this as a fallback.&lt;/p&gt;&quot;</span>)</span><br><span class="line">					.append(<span class="string">&quot;&lt;div id=&#x27;created&#x27;&gt;&quot;</span>).append(timestamp).append(<span class="string">&quot;&lt;/div&gt;&quot;</span>)</span><br><span class="line">					.append(<span class="string">&quot;&lt;div&gt;There was an unexpected error (type=&quot;</span>).append(htmlEscape(model.get(<span class="string">&quot;error&quot;</span>)))</span><br><span class="line">					.append(<span class="string">&quot;, status=&quot;</span>).append(htmlEscape(model.get(<span class="string">&quot;status&quot;</span>))).append(<span class="string">&quot;).&lt;/div&gt;&quot;</span>);</span><br><span class="line">			<span class="keyword">if</span> (message != <span class="literal">null</span>) &#123;</span><br><span class="line">				builder.append(<span class="string">&quot;&lt;div&gt;&quot;</span>).append(htmlEscape(message)).append(<span class="string">&quot;&lt;/div&gt;&quot;</span>);</span><br><span class="line">			&#125;</span><br><span class="line">			<span class="keyword">if</span> (trace != <span class="literal">null</span>) &#123;</span><br><span class="line">				builder.append(<span class="string">&quot;&lt;div style=&#x27;white-space:pre-wrap;&#x27;&gt;&quot;</span>).append(htmlEscape(trace)).append(<span class="string">&quot;&lt;/div&gt;&quot;</span>);</span><br><span class="line">			&#125;</span><br><span class="line">			builder.append(<span class="string">&quot;&lt;/body&gt;&lt;/html&gt;&quot;</span>);</span><br><span class="line">			response.getWriter().append(builder.toString());</span><br><span class="line">		&#125;</span><br><span class="line"></span><br><span class="line">		<span class="keyword">private</span> String <span class="title function_">htmlEscape</span><span class="params">(Object input)</span> &#123;</span><br><span class="line">			<span class="keyword">return</span> (input != <span class="literal">null</span>) ? HtmlUtils.htmlEscape(input.toString()) : <span class="literal">null</span>;</span><br><span class="line">		&#125;</span><br><span class="line"></span><br><span class="line">		<span class="keyword">private</span> String <span class="title function_">getMessage</span><span class="params">(Map&lt;String, ?&gt; model)</span> &#123;</span><br><span class="line">			<span class="type">Object</span> <span class="variable">path</span> <span class="operator">=</span> model.get(<span class="string">&quot;path&quot;</span>);</span><br><span class="line">			<span class="type">String</span> <span class="variable">message</span> <span class="operator">=</span> <span class="string">&quot;Cannot render error page for request [&quot;</span> + path + <span class="string">&quot;]&quot;</span>;</span><br><span class="line">			<span class="keyword">if</span> (model.get(<span class="string">&quot;message&quot;</span>) != <span class="literal">null</span>) &#123;</span><br><span class="line">				message += <span class="string">&quot; and exception [&quot;</span> + model.get(<span class="string">&quot;message&quot;</span>) + <span class="string">&quot;]&quot;</span>;</span><br><span class="line">			&#125;</span><br><span class="line">			message += <span class="string">&quot; as the response has already been committed.&quot;</span>;</span><br><span class="line">			message += <span class="string">&quot; As a result, the response may have the wrong status code.&quot;</span>;</span><br><span class="line">			<span class="keyword">return</span> message;</span><br><span class="line">		&#125;</span><br><span class="line"></span><br><span class="line">		<span class="meta">@Override</span></span><br><span class="line">		<span class="keyword">public</span> String <span class="title function_">getContentType</span><span class="params">()</span> &#123;</span><br><span class="line">			<span class="keyword">return</span> <span class="string">&quot;text/html&quot;</span>;</span><br><span class="line">		&#125;</span><br><span class="line"></span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h2 id="55、错误处理-【源码流程】几种异常处理原理"><a href="#55、错误处理-【源码流程】几种异常处理原理" class="headerlink" title="55、错误处理-【源码流程】几种异常处理原理"></a>55、错误处理-【源码流程】几种异常处理原理</h2><ul>
<li><p>自定义错误页</p>
<ul>
<li>error&#x2F;404.html   error&#x2F;5xx.html；有精确的错误状态码页面就匹配精确，没有就找 4xx.html；如果都没有就触发白页</li>
</ul>
</li>
<li><p><code>@ControllerAdvice</code>+<code>@ExceptionHandler</code>处理全局异常；底层是 <code>ExceptionHandlerExceptionResolver</code> 支持的</p>
</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="meta">@ControllerAdvice</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GlobalExceptionHandler</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ExceptionHandler(&#123;ArithmeticException.class,NullPointerException.class&#125;)</span>  <span class="comment">//处理异常</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">handleArithException</span><span class="params">(Exception e)</span>&#123;</span><br><span class="line"></span><br><span class="line">        log.error(<span class="string">&quot;异常是：&#123;&#125;&quot;</span>,e);</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;login&quot;</span>; <span class="comment">//视图地址</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ul>
<li><code>@ResponseStatus</code>+自定义异常 ；底层是 <code>ResponseStatusExceptionResolver</code> ，把responseStatus注解的信息底层调用 <code>response.sendError(statusCode, resolvedReason)</code>，tomcat发送的<code>/error</code></li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@ResponseStatus(value= HttpStatus.FORBIDDEN,reason = &quot;用户数量太多&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserTooManyException</span> <span class="keyword">extends</span> <span class="title class_">RuntimeException</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span>  <span class="title function_">UserTooManyException</span><span class="params">()</span>&#123;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">public</span>  <span class="title function_">UserTooManyException</span><span class="params">(String message)</span>&#123;</span><br><span class="line">        <span class="built_in">super</span>(message);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Controller</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TableController</span> &#123;</span><br><span class="line">    </span><br><span class="line">	<span class="meta">@GetMapping(&quot;/dynamic_table&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">dynamic_table</span><span class="params">(<span class="meta">@RequestParam(value=&quot;pn&quot;,defaultValue = &quot;1&quot;)</span> Integer pn,Model model)</span>&#123;</span><br><span class="line">        <span class="comment">//表格内容的遍历</span></span><br><span class="line">	     List&lt;User&gt; users = Arrays.asList(<span class="keyword">new</span> <span class="title class_">User</span>(<span class="string">&quot;zhangsan&quot;</span>, <span class="string">&quot;123456&quot;</span>),</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">User</span>(<span class="string">&quot;lisi&quot;</span>, <span class="string">&quot;123444&quot;</span>),</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">User</span>(<span class="string">&quot;haha&quot;</span>, <span class="string">&quot;aaaaa&quot;</span>),</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">User</span>(<span class="string">&quot;hehe &quot;</span>, <span class="string">&quot;aaddd&quot;</span>));</span><br><span class="line">        model.addAttribute(<span class="string">&quot;users&quot;</span>,users);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span>(users.size()&gt;<span class="number">3</span>)&#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">UserTooManyException</span>();<span class="comment">//抛出自定义异常</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;table/dynamic_table&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ul>
<li><p>Spring自家异常如 <code> org.springframework.web.bind.MissingServletRequestParameterException</code>，<code>DefaultHandlerExceptionResolver</code> 处理Spring自家异常。</p>
</li>
<li><ul>
<li><code>response.sendError(HttpServletResponse.SC_BAD_REQUEST/*400*/, ex.getMessage());</code></li>
</ul>
</li>
<li><p>自定义实现 <code>HandlerExceptionResolver</code> 处理异常；可以作为默认的全局异常处理规则</p>
</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Order(value= Ordered.HIGHEST_PRECEDENCE)</span>  <span class="comment">//优先级，数字越小优先级越高</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CustomerHandlerExceptionResolver</span> <span class="keyword">implements</span> <span class="title class_">HandlerExceptionResolver</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> ModelAndView <span class="title function_">resolveException</span><span class="params">(HttpServletRequest request,</span></span><br><span class="line"><span class="params">                                         HttpServletResponse response,</span></span><br><span class="line"><span class="params">                                         Object handler, Exception ex)</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            response.sendError(<span class="number">511</span>,<span class="string">&quot;我喜欢的错误&quot;</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ModelAndView</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ul>
<li><code>ErrorViewResolver</code>  实现自定义处理异常<ul>
<li><code>response.sendError()</code>，error请求就会转给controller。</li>
<li>你的异常没有任何人能处理，tomcat底层调用<code>response.sendError()</code>，error请求就会转给controller。</li>
<li><code>basicErrorController</code> 要去的页面地址是 <code>ErrorViewResolver</code>  。</li>
</ul>
</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Controller</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;$&#123;server.error.path:$&#123;error.path:/error&#125;&#125;&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BasicErrorController</span> <span class="keyword">extends</span> <span class="title class_">AbstractErrorController</span> &#123;</span><br><span class="line"></span><br><span class="line">    ...</span><br><span class="line">    </span><br><span class="line">	<span class="meta">@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)</span></span><br><span class="line">	<span class="keyword">public</span> ModelAndView <span class="title function_">errorHtml</span><span class="params">(HttpServletRequest request, HttpServletResponse response)</span> &#123;</span><br><span class="line">		<span class="type">HttpStatus</span> <span class="variable">status</span> <span class="operator">=</span> getStatus(request);</span><br><span class="line">		Map&lt;String, Object&gt; model = Collections</span><br><span class="line">				.unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));</span><br><span class="line">		response.setStatus(status.value());</span><br><span class="line">		<span class="type">ModelAndView</span> <span class="variable">modelAndView</span> <span class="operator">=</span> resolveErrorView(request, response, status, model);</span><br><span class="line">		<span class="keyword">return</span> (modelAndView != <span class="literal">null</span>) ? modelAndView : <span class="keyword">new</span> <span class="title class_">ModelAndView</span>(<span class="string">&quot;error&quot;</span>, model);</span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">protected</span> ModelAndView <span class="title function_">resolveErrorView</span><span class="params">(HttpServletRequest request, HttpServletResponse response, HttpStatus status,</span></span><br><span class="line"><span class="params">			Map&lt;String, Object&gt; model)</span> &#123;</span><br><span class="line">        <span class="comment">//这里用到ErrorViewResolver接口</span></span><br><span class="line">		<span class="keyword">for</span> (ErrorViewResolver resolver : <span class="built_in">this</span>.errorViewResolvers) &#123;</span><br><span class="line">			<span class="type">ModelAndView</span> <span class="variable">modelAndView</span> <span class="operator">=</span> resolver.resolveErrorView(request, status, model);</span><br><span class="line">			<span class="keyword">if</span> (modelAndView != <span class="literal">null</span>) &#123;</span><br><span class="line">				<span class="keyword">return</span> modelAndView;</span><br><span class="line">			&#125;</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">    ...</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@FunctionalInterface</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">ErrorViewResolver</span> &#123;</span><br><span class="line"></span><br><span class="line">	ModelAndView <span class="title function_">resolveErrorView</span><span class="params">(HttpServletRequest request, HttpStatus status, Map&lt;String, Object&gt; model)</span>;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h2 id="56、原生组件注入-原生注解与Spring方式注入"><a href="#56、原生组件注入-原生注解与Spring方式注入" class="headerlink" title="56、原生组件注入-原生注解与Spring方式注入"></a>56、原生组件注入-原生注解与Spring方式注入</h2><p><a target="_blank" rel="noopener" href="https://docs.spring.io/spring-boot/docs/2.4.2/reference/htmlsingle/#howto-add-a-servlet-filter-or-listener">官方文档 - Servlets, Filters, and listeners</a></p>
<h3 id="使用原生的注解"><a href="#使用原生的注解" class="headerlink" title="使用原生的注解"></a>使用原生的注解</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@WebServlet(urlPatterns = &quot;/my&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyServlet</span> <span class="keyword">extends</span> <span class="title class_">HttpServlet</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">doGet</span><span class="params">(HttpServletRequest req, HttpServletResponse resp)</span> <span class="keyword">throws</span> ServletException, IOException &#123;</span><br><span class="line">        resp.getWriter().write(<span class="string">&quot;66666&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="meta">@WebFilter(urlPatterns=&#123;&quot;/css/*&quot;,&quot;/images/*&quot;&#125;)</span> <span class="comment">//my</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyFilter</span> <span class="keyword">implements</span> <span class="title class_">Filter</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">init</span><span class="params">(FilterConfig filterConfig)</span> <span class="keyword">throws</span> ServletException &#123;</span><br><span class="line">        log.info(<span class="string">&quot;MyFilter初始化完成&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">doFilter</span><span class="params">(ServletRequest request, ServletResponse response, FilterChain chain)</span> <span class="keyword">throws</span> IOException, ServletException &#123;</span><br><span class="line">        log.info(<span class="string">&quot;MyFilter工作&quot;</span>);</span><br><span class="line">        chain.doFilter(request,response);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">destroy</span><span class="params">()</span> &#123;</span><br><span class="line">        log.info(<span class="string">&quot;MyFilter销毁&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="meta">@WebListener</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyServletContextListener</span> <span class="keyword">implements</span> <span class="title class_">ServletContextListener</span> &#123;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">contextInitialized</span><span class="params">(ServletContextEvent sce)</span> &#123;</span><br><span class="line">        log.info(<span class="string">&quot;MySwervletContextListener监听到项目初始化完成&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">contextDestroyed</span><span class="params">(ServletContextEvent sce)</span> &#123;</span><br><span class="line">        log.info(<span class="string">&quot;MySwervletContextListener监听到项目销毁&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>最后还要在主启动类添加注解<code>@ServletComponentScan</code></p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@ServletComponentScan(basePackages = &quot;com.lun&quot;)</span><span class="comment">//</span></span><br><span class="line"><span class="meta">@SpringBootApplication(exclude = RedisAutoConfiguration.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Boot05WebAdminApplication</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        SpringApplication.run(Boot05WebAdminApplication.class, args);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="Spring方式注入"><a href="#Spring方式注入" class="headerlink" title="Spring方式注入"></a>Spring方式注入</h3><p><code>ServletRegistrationBean</code>, <code>FilterRegistrationBean</code>, and <code>ServletListenerRegistrationBean</code></p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration(proxyBeanMethods = true)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyRegistConfig</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> ServletRegistrationBean <span class="title function_">myServlet</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="type">MyServlet</span> <span class="variable">myServlet</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MyServlet</span>();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ServletRegistrationBean</span>(myServlet,<span class="string">&quot;/my&quot;</span>,<span class="string">&quot;/my02&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> FilterRegistrationBean <span class="title function_">myFilter</span><span class="params">()</span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="type">MyFilter</span> <span class="variable">myFilter</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MyFilter</span>();</span><br><span class="line"><span class="comment">//        return new FilterRegistrationBean(myFilter,myServlet());</span></span><br><span class="line">        <span class="type">FilterRegistrationBean</span> <span class="variable">filterRegistrationBean</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FilterRegistrationBean</span>(myFilter);</span><br><span class="line">        filterRegistrationBean.setUrlPatterns(Arrays.asList(<span class="string">&quot;/my&quot;</span>,<span class="string">&quot;/css/*&quot;</span>));</span><br><span class="line">        <span class="keyword">return</span> filterRegistrationBean;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> ServletListenerRegistrationBean <span class="title function_">myListener</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="type">MySwervletContextListener</span> <span class="variable">mySwervletContextListener</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MySwervletContextListener</span>();</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ServletListenerRegistrationBean</span>(mySwervletContextListener);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h2 id="57、原生组件注入-【源码分析】DispatcherServlet注入原理"><a href="#57、原生组件注入-【源码分析】DispatcherServlet注入原理" class="headerlink" title="57、原生组件注入-【源码分析】DispatcherServlet注入原理"></a>57、原生组件注入-【源码分析】DispatcherServlet注入原理</h2><p><code>org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration</code>配置类</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)</span></span><br><span class="line"><span class="meta">@Configuration(proxyBeanMethods = false)</span></span><br><span class="line"><span class="meta">@ConditionalOnWebApplication(type = Type.SERVLET)</span></span><br><span class="line"><span class="meta">@ConditionalOnClass(DispatcherServlet.class)</span></span><br><span class="line"><span class="meta">@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DispatcherServletAutoConfiguration</span> &#123;</span><br><span class="line"></span><br><span class="line">	<span class="comment">/*</span></span><br><span class="line"><span class="comment">	 * The bean name for a DispatcherServlet that will be mapped to the root URL &quot;/&quot;</span></span><br><span class="line"><span class="comment">	 */</span></span><br><span class="line">	<span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">DEFAULT_DISPATCHER_SERVLET_BEAN_NAME</span> <span class="operator">=</span> <span class="string">&quot;dispatcherServlet&quot;</span>;</span><br><span class="line"></span><br><span class="line">	<span class="comment">/*</span></span><br><span class="line"><span class="comment">	 * The bean name for a ServletRegistrationBean for the DispatcherServlet &quot;/&quot;</span></span><br><span class="line"><span class="comment">	 */</span></span><br><span class="line">	<span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME</span> <span class="operator">=</span> <span class="string">&quot;dispatcherServletRegistration&quot;</span>;</span><br><span class="line"></span><br><span class="line">	<span class="meta">@Configuration(proxyBeanMethods = false)</span></span><br><span class="line">	<span class="meta">@Conditional(DefaultDispatcherServletCondition.class)</span></span><br><span class="line">	<span class="meta">@ConditionalOnClass(ServletRegistration.class)</span></span><br><span class="line">	<span class="meta">@EnableConfigurationProperties(WebMvcProperties.class)</span></span><br><span class="line">	<span class="keyword">protected</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">DispatcherServletConfiguration</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//创建DispatcherServlet类的Bean</span></span><br><span class="line">		<span class="meta">@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)</span></span><br><span class="line">		<span class="keyword">public</span> DispatcherServlet <span class="title function_">dispatcherServlet</span><span class="params">(WebMvcProperties webMvcProperties)</span> &#123;</span><br><span class="line">			<span class="type">DispatcherServlet</span> <span class="variable">dispatcherServlet</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DispatcherServlet</span>();</span><br><span class="line">			dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());</span><br><span class="line">			dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());</span><br><span class="line">			dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());</span><br><span class="line">			dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());</span><br><span class="line">			dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());</span><br><span class="line">			<span class="keyword">return</span> dispatcherServlet;</span><br><span class="line">		&#125;</span><br><span class="line"></span><br><span class="line">		<span class="meta">@Bean</span></span><br><span class="line">		<span class="meta">@ConditionalOnBean(MultipartResolver.class)</span></span><br><span class="line">		<span class="meta">@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)</span></span><br><span class="line">		<span class="keyword">public</span> MultipartResolver <span class="title function_">multipartResolver</span><span class="params">(MultipartResolver resolver)</span> &#123;</span><br><span class="line">			<span class="comment">// Detect if the user has created a MultipartResolver but named it incorrectly</span></span><br><span class="line">			<span class="keyword">return</span> resolver;</span><br><span class="line">		&#125;</span><br><span class="line"></span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Configuration(proxyBeanMethods = false)</span></span><br><span class="line">	<span class="meta">@Conditional(DispatcherServletRegistrationCondition.class)</span></span><br><span class="line">	<span class="meta">@ConditionalOnClass(ServletRegistration.class)</span></span><br><span class="line">	<span class="meta">@EnableConfigurationProperties(WebMvcProperties.class)</span></span><br><span class="line">	<span class="meta">@Import(DispatcherServletConfiguration.class)</span></span><br><span class="line">	<span class="keyword">protected</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">DispatcherServletRegistrationConfiguration</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//注册DispatcherServlet类</span></span><br><span class="line">		<span class="meta">@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)</span></span><br><span class="line">		<span class="meta">@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)</span></span><br><span class="line">		<span class="keyword">public</span> DispatcherServletRegistrationBean <span class="title function_">dispatcherServletRegistration</span><span class="params">(DispatcherServlet dispatcherServlet,</span></span><br><span class="line"><span class="params">				WebMvcProperties webMvcProperties, ObjectProvider&lt;MultipartConfigElement&gt; multipartConfig)</span> &#123;</span><br><span class="line">			<span class="type">DispatcherServletRegistrationBean</span> <span class="variable">registration</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DispatcherServletRegistrationBean</span>(dispatcherServlet,</span><br><span class="line">					webMvcProperties.getServlet().getPath());</span><br><span class="line">			registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);</span><br><span class="line">			registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());</span><br><span class="line">			multipartConfig.ifAvailable(registration::setMultipartConfig);</span><br><span class="line">			<span class="keyword">return</span> registration;</span><br><span class="line">		&#125;</span><br><span class="line"></span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">    ...</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>DispatcherServlet</code>默认映射的是 <code>/</code> 路径，可以通过在配置文件修改<code>spring.mvc.servlet.path=/mvc</code>。</p>
<h2 id="58、嵌入式Servlet容器-【源码分析】切换web服务器与定制化"><a href="#58、嵌入式Servlet容器-【源码分析】切换web服务器与定制化" class="headerlink" title="58、嵌入式Servlet容器-【源码分析】切换web服务器与定制化"></a>58、嵌入式Servlet容器-【源码分析】切换web服务器与定制化</h2><ul>
<li><p>默认支持的WebServer</p>
<ul>
<li><code>Tomcat</code>, <code>Jetty</code>, or <code>Undertow</code>。</li>
<li><code>ServletWebServerApplicationContext </code>容器启动寻找<code>ServletWebServerFactory</code> 并引导创建服务器。</li>
</ul>
</li>
<li><p>原理</p>
<ul>
<li>SpringBoot应用启动发现当前是Web应用，web场景包-导入tomcat。</li>
<li>web应用会创建一个web版的IOC容器 <code>ServletWebServerApplicationContext</code> 。</li>
<li><code>ServletWebServerApplicationContext</code>  启动的时候寻找 <code>ServletWebServerFactory</code> （Servlet 的web服务器工厂——&gt;Servlet 的web服务器）。</li>
<li>SpringBoot底层默认有很多的WebServer工厂（<code>ServletWebServerFactoryConfiguration</code>内创建Bean），如：<ul>
<li><code>TomcatServletWebServerFactory</code></li>
<li><code>JettyServletWebServerFactory</code></li>
<li><code>UndertowServletWebServerFactory</code></li>
</ul>
</li>
<li>底层直接会有一个自动配置类<code>ServletWebServerFactoryAutoConfiguration</code>。</li>
<li><code>ServletWebServerFactoryAutoConfiguration</code>导入了<code>ServletWebServerFactoryConfiguration</code>（配置类）。</li>
<li><code>ServletWebServerFactoryConfiguration  </code>根据动态判断系统中到底导入了那个Web服务器的包。（默认是web-starter导入tomcat包），容器中就有 <code>TomcatServletWebServerFactory</code></li>
<li><code>TomcatServletWebServerFactory </code>创建出Tomcat服务器并启动；<code>TomcatWebServer</code> 的构造器拥有初始化方法initialize——<code>this.tomcat.start();</code></li>
<li>内嵌服务器，与以前手动把启动服务器相比，改成现在使用代码启动（tomcat核心jar包存在）。</li>
</ul>
</li>
</ul>
<p>Spring Boot默认使用Tomcat服务器，若需更改其他服务器，则修改工程pom.xml：</p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">exclusions</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">exclusion</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-tomcat<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">exclusion</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">exclusions</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-jetty<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p><a target="_blank" rel="noopener" href="https://docs.spring.io/spring-boot/docs/2.4.2/reference/htmlsingle/#howto-use-another-web-server">官方文档 - Use Another Web Server</a></p>
<h3 id="定制Servlet容器"><a href="#定制Servlet容器" class="headerlink" title="定制Servlet容器"></a>定制Servlet容器</h3><ul>
<li><p>实现<code>WebServerFactoryCustomizer&lt;ConfigurableServletWebServerFactory&gt;</code> </p>
</li>
<li><ul>
<li>把配置文件的值和<code>ServletWebServerFactory</code>进行绑定</li>
</ul>
</li>
<li><p>修改配置文件 <code>server.xxx</code></p>
</li>
<li><p>直接自定义 <code>ConfigurableServletWebServerFactory</code></p>
</li>
</ul>
<p><code>xxxxxCustomizer</code>：定制化器，可以改变xxxx的默认规则</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> org.springframework.boot.web.server.WebServerFactoryCustomizer;</span><br><span class="line"><span class="keyword">import</span> org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;</span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Component;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CustomizationBean</span> <span class="keyword">implements</span> <span class="title class_">WebServerFactoryCustomizer</span>&lt;ConfigurableServletWebServerFactory&gt; &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">customize</span><span class="params">(ConfigurableServletWebServerFactory server)</span> &#123;</span><br><span class="line">        server.setPort(<span class="number">9000</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h2 id="59、定制化原理-SpringBoot定制化组件的几种方式（小结）"><a href="#59、定制化原理-SpringBoot定制化组件的几种方式（小结）" class="headerlink" title="59、定制化原理-SpringBoot定制化组件的几种方式（小结）"></a>59、定制化原理-SpringBoot定制化组件的几种方式（小结）</h2><h3 id="定制化的常见方式"><a href="#定制化的常见方式" class="headerlink" title="定制化的常见方式"></a>定制化的常见方式</h3><ul>
<li><p>修改配置文件</p>
</li>
<li><p><code>xxxxxCustomizer</code></p>
</li>
<li><p>编写自定义的配置类  <code>xxxConfiguration</code> + <code>@Bean</code>替换、增加容器中默认组件，视图解析器</p>
</li>
<li><p>Web应用 编写一个配置类实现 <code>WebMvcConfigurer</code> 即可定制化web功能 + <code>@Bean</code>给容器中再扩展一些组件</p>
</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AdminWebConfig</span> <span class="keyword">implements</span> <span class="title class_">WebMvcConfigurer</span>&#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ul>
<li><code>@EnableWebMvc</code> + <code>WebMvcConfigurer</code> — <code>@Bean</code>  可以全面接管SpringMVC，所有规则全部自己重新配置； 实现定制和扩展功能（<strong>高级功能，初学者退避三舍</strong>）。<ul>
<li>原理：<ol>
<li><code>WebMvcAutoConfiguration</code>默认的SpringMVC的自动配置功能类，如静态资源、欢迎页等。</li>
<li>一旦使用 <code>@EnableWebMvc</code> ，会<code>@Import(DelegatingWebMvcConfiguration.class)</code>。</li>
<li><code>DelegatingWebMvcConfiguration</code>的作用，只保证SpringMVC最基本的使用<ul>
<li>把所有系统中的<code>WebMvcConfigurer</code>拿过来，所有功能的定制都是这些<code>WebMvcConfigurer</code>合起来一起生效。</li>
<li>自动配置了一些非常底层的组件，如<code>RequestMappingHandlerMapping</code>，这些组件依赖的组件都是从容器中获取如。</li>
<li><code>public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport</code>。</li>
</ul>
</li>
<li><code>WebMvcAutoConfiguration</code>里面的配置要能生效必须  <code>@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)</code>。</li>
<li>@EnableWebMvc 导致了WebMvcAutoConfiguration  没有生效。</li>
</ol>
</li>
</ul>
</li>
</ul>
<h3 id="原理分析套路"><a href="#原理分析套路" class="headerlink" title="原理分析套路"></a>原理分析套路</h3><p>场景starter - <code>xxxxAutoConfiguration</code> - 导入xxx组件 - 绑定<code>xxxProperties</code> - 绑定配置文件项。</p>
<h2 id="60、数据访问-数据库场景的自动配置分析与整合测试"><a href="#60、数据访问-数据库场景的自动配置分析与整合测试" class="headerlink" title="60、数据访问-数据库场景的自动配置分析与整合测试"></a>60、数据访问-数据库场景的自动配置分析与整合测试</h2><h3 id="导入JDBC场景"><a href="#导入JDBC场景" class="headerlink" title="导入JDBC场景"></a>导入JDBC场景</h3><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-data-jdbc<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>接着导入数据库驱动包（MySQL为例）。</p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="comment">&lt;!--默认版本：--&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">mysql.version</span>&gt;</span>8.0.22<span class="tag">&lt;/<span class="name">mysql.version</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>mysql<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>mysql-connector-java<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!--&lt;version&gt;5.1.49&lt;/version&gt;--&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">&lt;!--</span></span><br><span class="line"><span class="comment">想要修改版本</span></span><br><span class="line"><span class="comment">1、直接依赖引入具体版本（maven的就近依赖原则）</span></span><br><span class="line"><span class="comment">2、重新声明版本（maven的属性的就近优先原则）</span></span><br><span class="line"><span class="comment">--&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">properties</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">java.version</span>&gt;</span>1.8<span class="tag">&lt;/<span class="name">java.version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">mysql.version</span>&gt;</span>5.1.49<span class="tag">&lt;/<span class="name">mysql.version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">properties</span>&gt;</span></span><br></pre></td></tr></table></figure>



<h3 id="相关数据源配置类"><a href="#相关数据源配置类" class="headerlink" title="相关数据源配置类"></a>相关数据源配置类</h3><ul>
<li><p><code>DataSourceAutoConfiguration</code> ： 数据源的自动配置。</p>
<ul>
<li>修改数据源相关的配置：<code>spring.datasource</code>。</li>
<li><strong>数据库连接池的配置，是自己容器中没有DataSource才自动配置的</strong>。</li>
<li>底层配置好的连接池是：<code>HikariDataSource</code>。</li>
</ul>
</li>
<li><p><code>DataSourceTransactionManagerAutoConfiguration</code>： 事务管理器的自动配置。</p>
</li>
<li><p><code>JdbcTemplateAutoConfiguration</code>： <code>JdbcTemplate</code>的自动配置，可以来对数据库进行CRUD。</p>
<ul>
<li>可以修改前缀为<code>spring.jdbc</code>的配置项来修改<code>JdbcTemplate</code>。</li>
<li><code>@Bean @Primary JdbcTemplate</code>：Spring容器中有这个<code>JdbcTemplate</code>组件，使用<code>@Autowired</code>。</li>
</ul>
</li>
<li><p><code>JndiDataSourceAutoConfiguration</code>： JNDI的自动配置。</p>
</li>
<li><p><code>XADataSourceAutoConfiguration</code>： 分布式事务相关的。</p>
</li>
</ul>
<h3 id="修改配置项"><a href="#修改配置项" class="headerlink" title="修改配置项"></a>修改配置项</h3><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">datasource:</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">jdbc:mysql://localhost:3306/db_account</span></span><br><span class="line">    <span class="attr">username:</span> <span class="string">root</span></span><br><span class="line">    <span class="attr">password:</span> <span class="number">123456</span></span><br><span class="line">    <span class="attr">driver-class-name:</span> <span class="string">com.mysql.jdbc.Driver</span></span><br></pre></td></tr></table></figure>



<h3 id="单元测试数据源"><a href="#单元测试数据源" class="headerlink" title="单元测试数据源"></a>单元测试数据源</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> org.junit.jupiter.api.Test;</span><br><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.Autowired;</span><br><span class="line"><span class="keyword">import</span> org.springframework.boot.test.context.SpringBootTest;</span><br><span class="line"><span class="keyword">import</span> org.springframework.jdbc.core.JdbcTemplate;</span><br><span class="line"></span><br><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Boot05WebAdminApplicationTests</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    JdbcTemplate jdbcTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span><span class="comment">//用@org.junit.Test会报空指针异常，可能跟JUnit新版本有关</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">contextLoads</span><span class="params">()</span> &#123;</span><br><span class="line"><span class="comment">//        jdbcTemplate.queryForObject(&quot;select * from account_tbl&quot;)</span></span><br><span class="line"><span class="comment">//        jdbcTemplate.queryForList(&quot;select * from account_tbl&quot;,)</span></span><br><span class="line">        <span class="type">Long</span> <span class="variable">aLong</span> <span class="operator">=</span> jdbcTemplate.queryForObject(<span class="string">&quot;select count(*) from account_tbl&quot;</span>, Long.class);</span><br><span class="line">        log.info(<span class="string">&quot;记录总数：&#123;&#125;&quot;</span>,aLong);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h2 id="61、数据访问-自定义方式整合druid数据源"><a href="#61、数据访问-自定义方式整合druid数据源" class="headerlink" title="61、数据访问-自定义方式整合druid数据源"></a>61、数据访问-自定义方式整合druid数据源</h2><p><a target="_blank" rel="noopener" href="https://github.com/alibaba/druid">Druid官网</a></p>
<h3 id="Druid是什么？"><a href="#Druid是什么？" class="headerlink" title="Druid是什么？"></a>Druid是什么？</h3><p>它是数据库连接池，它能够提供强大的监控和扩展功能。</p>
<p><a target="_blank" rel="noopener" href="https://github.com/alibaba/druid/wiki/Druid%E8%BF%9E%E6%8E%A5%E6%B1%A0%E4%BB%8B%E7%BB%8D">官方文档 - Druid连接池介绍</a></p>
<p>Spring Boot整合第三方技术的两种方式：</p>
<ul>
<li><p>自定义</p>
</li>
<li><p>找starter场景</p>
</li>
</ul>
<h3 id="自定义方式"><a href="#自定义方式" class="headerlink" title="自定义方式"></a>自定义方式</h3><p><strong>添加依赖</strong>：</p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.alibaba<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>druid<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.1.17<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p><strong>配置Druid数据源</strong>：</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyConfig</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="meta">@ConfigurationProperties(&quot;spring.datasource&quot;)</span><span class="comment">//复用配置文件的数据源配置</span></span><br><span class="line">    <span class="keyword">public</span> DataSource <span class="title function_">dataSource</span><span class="params">()</span> <span class="keyword">throws</span> SQLException &#123;</span><br><span class="line">        <span class="type">DruidDataSource</span> <span class="variable">druidDataSource</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DruidDataSource</span>();</span><br><span class="line"></span><br><span class="line"><span class="comment">//        druidDataSource.setUrl();</span></span><br><span class="line"><span class="comment">//        druidDataSource.setUsername();</span></span><br><span class="line"><span class="comment">//        druidDataSource.setPassword();</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> druidDataSource;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><a target="_blank" rel="noopener" href="https://github.com/alibaba/druid/wiki/DruidDataSource%E9%85%8D%E7%BD%AE">更多配置项</a></p>
<p><strong>配置Druid的监控页功能</strong>：</p>
<ul>
<li><p>Druid内置提供了一个<code>StatViewServlet</code>用于展示Druid的统计信息。<a target="_blank" rel="noopener" href="https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_StatViewServlet%E9%85%8D%E7%BD%AE">官方文档 - 配置_StatViewServlet配置</a>。这个<code>StatViewServlet</code>的用途包括：</p>
<ul>
<li>提供监控信息展示的html页面</li>
<li>提供监控信息的JSON API</li>
</ul>
</li>
<li><p>Druid内置提供一个<code>StatFilter</code>，用于统计监控信息。<a target="_blank" rel="noopener" href="https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_StatFilter">官方文档 - 配置_StatFilter</a></p>
</li>
<li><p><code>WebStatFilter</code>用于采集web-jdbc关联监控的数据，如SQL监控、URI监控。<a target="_blank" rel="noopener" href="https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_%E9%85%8D%E7%BD%AEWebStatFilter">官方文档 - 配置_配置WebStatFilter</a></p>
</li>
<li><p>Druid提供了<code>WallFilter</code>，它是基于SQL语义分析来实现防御SQL注入攻击的。<a target="_blank" rel="noopener" href="https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE-wallfilter">官方文档 - 配置 wallfilter</a></p>
</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyConfig</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="meta">@ConfigurationProperties(&quot;spring.datasource&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> DataSource <span class="title function_">dataSource</span><span class="params">()</span> <span class="keyword">throws</span> SQLException &#123;</span><br><span class="line">        <span class="type">DruidDataSource</span> <span class="variable">druidDataSource</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DruidDataSource</span>();</span><br><span class="line"></span><br><span class="line">        <span class="comment">//加入监控和防火墙功能功能</span></span><br><span class="line">        druidDataSource.setFilters(<span class="string">&quot;stat,wall&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> druidDataSource;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 配置 druid的监控页功能</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> ServletRegistrationBean <span class="title function_">statViewServlet</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="type">StatViewServlet</span> <span class="variable">statViewServlet</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StatViewServlet</span>();</span><br><span class="line">        ServletRegistrationBean&lt;StatViewServlet&gt; registrationBean = </span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">ServletRegistrationBean</span>&lt;&gt;(statViewServlet, <span class="string">&quot;/druid/*&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//监控页账号密码：</span></span><br><span class="line">        registrationBean.addInitParameter(<span class="string">&quot;loginUsername&quot;</span>,<span class="string">&quot;admin&quot;</span>);</span><br><span class="line">        registrationBean.addInitParameter(<span class="string">&quot;loginPassword&quot;</span>,<span class="string">&quot;123456&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> registrationBean;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">     <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * WebStatFilter 用于采集web-jdbc关联监控的数据。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> FilterRegistrationBean <span class="title function_">webStatFilter</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="type">WebStatFilter</span> <span class="variable">webStatFilter</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">WebStatFilter</span>();</span><br><span class="line"></span><br><span class="line">        FilterRegistrationBean&lt;WebStatFilter&gt; filterRegistrationBean = <span class="keyword">new</span> <span class="title class_">FilterRegistrationBean</span>&lt;&gt;(webStatFilter);</span><br><span class="line">        filterRegistrationBean.setUrlPatterns(Arrays.asList(<span class="string">&quot;/*&quot;</span>));</span><br><span class="line">        filterRegistrationBean.addInitParameter(<span class="string">&quot;exclusions&quot;</span>,<span class="string">&quot;*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> filterRegistrationBean;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h2 id="62、数据访问-druid数据源starter整合方式"><a href="#62、数据访问-druid数据源starter整合方式" class="headerlink" title="62、数据访问-druid数据源starter整合方式"></a>62、数据访问-druid数据源starter整合方式</h2><p><a href="">官方文档 - Druid Spring Boot Starter</a></p>
<p><strong>引入依赖</strong>：</p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.alibaba<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>druid-spring-boot-starter<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.1.17<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p><strong>分析自动配置</strong>：</p>
<ul>
<li>扩展配置项 <code>spring.datasource.druid</code></li>
<li>自动配置类<code>DruidDataSourceAutoConfigure</code></li>
<li><code>DruidSpringAopConfiguration.class</code>,  监控SpringBean的；配置项：<code>spring.datasource.druid.aop-patterns</code></li>
<li><code>DruidStatViewServletConfiguration.class</code>, 监控页的配置。<code>spring.datasource.druid.stat-view-servlet</code>默认开启。</li>
<li><code>DruidWebStatFilterConfiguration.class</code>，web监控配置。<code>spring.datasource.druid.web-stat-filter</code>默认开启。</li>
<li><code>DruidFilterConfiguration.class</code>所有Druid的filter的配置：</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">FILTER_STAT_PREFIX</span> <span class="operator">=</span> <span class="string">&quot;spring.datasource.druid.filter.stat&quot;</span>;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">FILTER_CONFIG_PREFIX</span> <span class="operator">=</span> <span class="string">&quot;spring.datasource.druid.filter.config&quot;</span>;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">FILTER_ENCODING_PREFIX</span> <span class="operator">=</span> <span class="string">&quot;spring.datasource.druid.filter.encoding&quot;</span>;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">FILTER_SLF4J_PREFIX</span> <span class="operator">=</span> <span class="string">&quot;spring.datasource.druid.filter.slf4j&quot;</span>;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">FILTER_LOG4J_PREFIX</span> <span class="operator">=</span> <span class="string">&quot;spring.datasource.druid.filter.log4j&quot;</span>;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">FILTER_LOG4J2_PREFIX</span> <span class="operator">=</span> <span class="string">&quot;spring.datasource.druid.filter.log4j2&quot;</span>;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">FILTER_COMMONS_LOG_PREFIX</span> <span class="operator">=</span> <span class="string">&quot;spring.datasource.druid.filter.commons-log&quot;</span>;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">FILTER_WALL_PREFIX</span> <span class="operator">=</span> <span class="string">&quot;spring.datasource.druid.filter.wall&quot;</span>;</span><br></pre></td></tr></table></figure>

<p><strong>配置示例</strong>：</p>
<figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">datasource:</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">jdbc:mysql://localhost:3306/db_account</span></span><br><span class="line">    <span class="attr">username:</span> <span class="string">root</span></span><br><span class="line">    <span class="attr">password:</span> <span class="number">123456</span></span><br><span class="line">    <span class="attr">driver-class-name:</span> <span class="string">com.mysql.jdbc.Driver</span></span><br><span class="line"></span><br><span class="line">    <span class="attr">druid:</span></span><br><span class="line">      <span class="attr">aop-patterns:</span> <span class="string">com.atguigu.admin.*</span>  <span class="comment">#监控SpringBean</span></span><br><span class="line">      <span class="attr">filters:</span> <span class="string">stat,wall</span>     <span class="comment"># 底层开启功能，stat（sql监控），wall（防火墙）</span></span><br><span class="line"></span><br><span class="line">      <span class="attr">stat-view-servlet:</span>   <span class="comment"># 配置监控页功能</span></span><br><span class="line">        <span class="attr">enabled:</span> <span class="literal">true</span></span><br><span class="line">        <span class="attr">login-username:</span> <span class="string">admin</span></span><br><span class="line">        <span class="attr">login-password:</span> <span class="string">admin</span></span><br><span class="line">        <span class="attr">resetEnable:</span> <span class="literal">false</span></span><br><span class="line"></span><br><span class="line">      <span class="attr">web-stat-filter:</span>  <span class="comment"># 监控web</span></span><br><span class="line">        <span class="attr">enabled:</span> <span class="literal">true</span></span><br><span class="line">        <span class="attr">urlPattern:</span> <span class="string">/*</span></span><br><span class="line">        <span class="attr">exclusions:</span> <span class="string">&#x27;*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*&#x27;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">      <span class="attr">filter:</span></span><br><span class="line">        <span class="attr">stat:</span>    <span class="comment"># 对上面filters里面的stat的详细配置</span></span><br><span class="line">          <span class="attr">slow-sql-millis:</span> <span class="number">1000</span></span><br><span class="line">          <span class="attr">logSlowSql:</span> <span class="literal">true</span></span><br><span class="line">          <span class="attr">enabled:</span> <span class="literal">true</span></span><br><span class="line">        <span class="attr">wall:</span></span><br><span class="line">          <span class="attr">enabled:</span> <span class="literal">true</span></span><br><span class="line">          <span class="attr">config:</span></span><br><span class="line">            <span class="attr">drop-table-allow:</span> <span class="literal">false</span></span><br></pre></td></tr></table></figure>



<h2 id="63、数据访问-整合MyBatis-配置版"><a href="#63、数据访问-整合MyBatis-配置版" class="headerlink" title="63、数据访问-整合MyBatis-配置版"></a>63、数据访问-整合MyBatis-配置版</h2><p><a target="_blank" rel="noopener" href="https://github.com/mybatis">MyBatis的GitHub仓库</a></p>
<p><a target="_blank" rel="noopener" href="https://mybatis.org/mybatis-3/zh/index.html">MyBatis官方</a></p>
<p><strong>starter的命名方式</strong>：</p>
<ol>
<li>SpringBoot官方的Starter：<code>spring-boot-starter-*</code></li>
<li>第三方的： <code>*-spring-boot-starter</code></li>
</ol>
<p><strong>引入依赖</strong>：</p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.mybatis.spring.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>mybatis-spring-boot-starter<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.1.4<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p><strong>配置模式</strong>:</p>
<ul>
<li><p>全局配置文件</p>
</li>
<li><p><code>SqlSessionFactory</code>：自动配置好了</p>
</li>
<li><p><code>SqlSession</code>：自动配置了<code>SqlSessionTemplate</code> 组合了<code>SqlSession</code></p>
</li>
<li><p><code>@Import(AutoConfiguredMapperScannerRegistrar.class)</code></p>
</li>
<li><p><code>Mapper</code>： 只要我们写的操作MyBatis的接口标准了<code>@Mapper</code>就会被自动扫描进来</p>
</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@EnableConfigurationProperties(MybatisProperties.class)</span> ： MyBatis配置项绑定类。</span><br><span class="line"><span class="meta">@AutoConfigureAfter(&#123; DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class &#125;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MybatisAutoConfiguration</span>&#123;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@ConfigurationProperties(prefix = &quot;mybatis&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MybatisProperties</span>&#123;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<hr>
<p><strong>配置文件</strong>：</p>
<figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">datasource:</span></span><br><span class="line">    <span class="attr">username:</span> <span class="string">root</span></span><br><span class="line">    <span class="attr">password:</span> <span class="number">1234</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">jdbc:mysql://localhost:3306/my</span></span><br><span class="line">    <span class="attr">driver-class-name:</span> <span class="string">com.mysql.jdbc.Driver</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 配置mybatis规则</span></span><br><span class="line"><span class="attr">mybatis:</span></span><br><span class="line">  <span class="attr">config-location:</span> <span class="string">classpath:mybatis/mybatis-config.xml</span>  <span class="comment">#全局配置文件位置</span></span><br><span class="line">  <span class="attr">mapper-locations:</span> <span class="string">classpath:mybatis/*.xml</span>  <span class="comment">#sql映射文件位置</span></span><br></pre></td></tr></table></figure>



<p><strong>mybatis-config.xml</strong>:</p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=<span class="string">&quot;1.0&quot;</span> encoding=<span class="string">&quot;UTF-8&quot;</span> ?&gt;</span></span><br><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">configuration</span></span></span><br><span class="line"><span class="meta">  <span class="keyword">PUBLIC</span> <span class="string">&quot;-//mybatis.org//DTD Config 3.0//EN&quot;</span></span></span><br><span class="line"><span class="meta">  <span class="string">&quot;http://mybatis.org/dtd/mybatis-3-config.dtd&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">	</span><br><span class="line">    <span class="comment">&lt;!-- 由于Spring Boot自动配置缘故，此处不必配置，只用来做做样。--&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br></pre></td></tr></table></figure>



<p><strong>Mapper接口</strong>：</p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=<span class="string">&quot;1.0&quot;</span> encoding=<span class="string">&quot;UTF-8&quot;</span> ?&gt;</span></span><br><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">mapper</span></span></span><br><span class="line"><span class="meta">        <span class="keyword">PUBLIC</span> <span class="string">&quot;-//mybatis.org//DTD Mapper 3.0//EN&quot;</span></span></span><br><span class="line"><span class="meta">        <span class="string">&quot;http://mybatis.org/dtd/mybatis-3-mapper.dtd&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">mapper</span> <span class="attr">namespace</span>=<span class="string">&quot;com.lun.boot.mapper.UserMapper&quot;</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;getUser&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.lun.boot.bean.User&quot;</span>&gt;</span></span><br><span class="line">        select * from user where id=#&#123;id&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">mapper</span>&gt;</span></span><br></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> com.lun.boot.bean.User;</span><br><span class="line"><span class="keyword">import</span> org.apache.ibatis.annotations.Mapper;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Mapper</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserMapper</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">getUser</span><span class="params">(Integer id)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><strong>POJO</strong>：</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> Integer id;</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    </span><br><span class="line">	<span class="comment">//getters and setters...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><strong>DB</strong>：</p>
<figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> `<span class="keyword">user</span>` (</span><br><span class="line">  `id` <span class="type">int</span>(<span class="number">11</span>) <span class="keyword">NOT</span> <span class="keyword">NULL</span> AUTO_INCREMENT,</span><br><span class="line">  `name` <span class="type">varchar</span>(<span class="number">45</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span>,</span><br><span class="line">  <span class="keyword">PRIMARY</span> KEY (`id`)</span><br><span class="line">) ENGINE<span class="operator">=</span>InnoDB AUTO_INCREMENT<span class="operator">=</span><span class="number">3</span> <span class="keyword">DEFAULT</span> CHARSET<span class="operator">=</span>utf8mb4;</span><br></pre></td></tr></table></figure>



<p><strong>Controller and Service</strong>：</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Controller</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ResponseBody</span></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/user/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">getUser</span><span class="params">(<span class="meta">@PathVariable(&quot;id&quot;)</span> Integer id)</span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> userService.getUser(id);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserMapper userMapper;<span class="comment">//IDEA下标红线，可忽视这红线</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">getUser</span><span class="params">(Integer id)</span>&#123;</span><br><span class="line">        <span class="keyword">return</span> userMapper.getUser(id);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>配置<code>private Configuration configuration;</code> 也就是配置<code>mybatis.configuration</code>相关的，就是相当于改mybatis全局配置文件中的值。（也就是说配置了<code>mybatis.configuration</code>，就不需配置mybatis全局配置文件了）</p>
<figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 配置mybatis规则</span></span><br><span class="line"><span class="attr">mybatis:</span></span><br><span class="line">  <span class="attr">mapper-locations:</span> <span class="string">classpath:mybatis/mapper/*.xml</span></span><br><span class="line">  <span class="comment"># 可以不写全局配置文件，所有全局配置文件的配置都放在configuration配置项中了。</span></span><br><span class="line">  <span class="comment"># config-location: classpath:mybatis/mybatis-config.xml</span></span><br><span class="line">  <span class="attr">configuration:</span></span><br><span class="line">    <span class="attr">map-underscore-to-camel-case:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure>



<h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><ul>
<li>导入MyBatis官方Starter。</li>
<li>编写Mapper接口，需<code>@Mapper</code>注解。</li>
<li>编写SQL映射文件并绑定Mapper接口。</li>
<li>在<code>application.yaml</code>中指定Mapper配置文件的所处位置，以及指定全局配置文件的信息 （建议：**配置在<code>mybatis.configuration</code>**）。</li>
</ul>
<h2 id="64、数据访问-整合MyBatis-注解配置混合版"><a href="#64、数据访问-整合MyBatis-注解配置混合版" class="headerlink" title="64、数据访问-整合MyBatis-注解配置混合版"></a>64、数据访问-整合MyBatis-注解配置混合版</h2><p>你可以通过Spring Initializr添加MyBatis的Starer。</p>
<p><strong>注解与配置混合搭配，干活不累</strong>：</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Mapper</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserMapper</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">getUser</span><span class="params">(Integer id)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Select(&quot;select * from user where id=#&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">getUser2</span><span class="params">(Integer id)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">saveUser</span><span class="params">(User user)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Insert(&quot;insert into user(`name`) values(#&#123;name&#125;)&quot;)</span></span><br><span class="line">    <span class="meta">@Options(useGeneratedKeys = true, keyProperty = &quot;id&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">saveUser2</span><span class="params">(User user)</span>;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=<span class="string">&quot;1.0&quot;</span> encoding=<span class="string">&quot;UTF-8&quot;</span> ?&gt;</span></span><br><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">mapper</span></span></span><br><span class="line"><span class="meta">        <span class="keyword">PUBLIC</span> <span class="string">&quot;-//mybatis.org//DTD Mapper 3.0//EN&quot;</span></span></span><br><span class="line"><span class="meta">        <span class="string">&quot;http://mybatis.org/dtd/mybatis-3-mapper.dtd&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">mapper</span> <span class="attr">namespace</span>=<span class="string">&quot;com.lun.boot.mapper.UserMapper&quot;</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;getUser&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.lun.boot.bean.User&quot;</span>&gt;</span></span><br><span class="line">        select * from user where id=#&#123;id&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">insert</span> <span class="attr">id</span>=<span class="string">&quot;saveUser&quot;</span> <span class="attr">useGeneratedKeys</span>=<span class="string">&quot;true&quot;</span> <span class="attr">keyProperty</span>=<span class="string">&quot;id&quot;</span>&gt;</span></span><br><span class="line">        insert into user(`name`) values(#&#123;name&#125;)</span><br><span class="line">    <span class="tag">&lt;/<span class="name">insert</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">mapper</span>&gt;</span></span><br></pre></td></tr></table></figure>



<ul>
<li><p>简单DAO方法就写在注解上。复杂的就写在配置文件里。</p>
</li>
<li><p>使用<code>@MapperScan(&quot;com.lun.boot.mapper&quot;)</code> 简化，Mapper接口就可以不用标注<code>@Mapper</code>注解。</p>
</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@MapperScan(&quot;com.lun.boot.mapper&quot;)</span></span><br><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MainApplication</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        SpringApplication.run(MainApplication.class, args);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h2 id="65、数据访问-整合MyBatisPlus操作数据库"><a href="#65、数据访问-整合MyBatisPlus操作数据库" class="headerlink" title="65、数据访问-整合MyBatisPlus操作数据库"></a>65、数据访问-整合MyBatisPlus操作数据库</h2><p><a target="_blank" rel="noopener" href="https://plugins.jetbrains.com/plugin/10119-mybatisx">IDEA的MyBatis的插件 - MyBatisX</a></p>
<p><a target="_blank" rel="noopener" href="https://baomidou.com/">MyBatisPlus官网</a></p>
<p><a target="_blank" rel="noopener" href="https://baomidou.com/guide/">MyBatisPlus官方文档</a></p>
<h3 id="MyBatisPlus是什么"><a href="#MyBatisPlus是什么" class="headerlink" title="MyBatisPlus是什么"></a>MyBatisPlus是什么</h3><p><a target="_blank" rel="noopener" href="https://github.com/baomidou/mybatis-plus">MyBatis-Plus</a>（简称 MP）是一个 <a target="_blank" rel="noopener" href="http://www.mybatis.org/mybatis-3/">MyBatis</a>的增强工具，在 MyBatis 的基础上只做增强不做改变，为简化开发、提高效率而生。</p>
<hr>
<p>添加依赖：</p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.baomidou<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>mybatis-plus-boot-starter<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>3.4.1<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure>

<ul>
<li><p><code>MybatisPlusAutoConfiguration</code>配置类，<code>MybatisPlusProperties</code>配置项绑定。</p>
</li>
<li><p><code>SqlSessionFactory</code>自动配置好，底层是容器中默认的数据源。</p>
</li>
<li><p><code>mapperLocations</code>自动配置好的，有默认值<code>classpath*:/mapper/**/*.xml</code>，这表示任意包的类路径下的所有mapper文件夹下任意路径下的所有xml都是sql映射文件。  建议以后sql映射文件放在 mapper下。</p>
</li>
<li><p>容器中也自动配置好了<code>SqlSessionTemplate</code>。</p>
</li>
<li><p><code>@Mapper</code> 标注的接口也会被自动扫描，建议直接 <code>@MapperScan(&quot;com.lun.boot.mapper&quot;)</code>批量扫描。</p>
</li>
<li><p>MyBatisPlus<strong>优点</strong>之一：只需要我们的Mapper继承MyBatisPlus的<code>BaseMapper</code> 就可以拥有CRUD能力，减轻开发工作。</p>
</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> com.baomidou.mybatisplus.core.mapper.BaseMapper;</span><br><span class="line"><span class="keyword">import</span> com.lun.hellomybatisplus.model.User;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserMapper</span> <span class="keyword">extends</span> <span class="title class_">BaseMapper</span>&lt;User&gt; &#123;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h2 id="66、数据访问-CRUD实验-数据列表展示"><a href="#66、数据访问-CRUD实验-数据列表展示" class="headerlink" title="66、数据访问-CRUD实验-数据列表展示"></a>66、数据访问-CRUD实验-数据列表展示</h2><p><a target="_blank" rel="noopener" href="https://baomidou.com/guide/crud-interface.html">官方文档 - CRUD接口</a></p>
<p>使用MyBatis Plus提供的<code>IService</code>，<code>ServiceImpl</code>，减轻Service层开发工作。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> com.lun.hellomybatisplus.model.User;</span><br><span class="line"><span class="keyword">import</span> com.baomidou.mybatisplus.extension.service.IService;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> *  Service 的CRUD也不用写了</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserService</span> <span class="keyword">extends</span> <span class="title class_">IService</span>&lt;User&gt; &#123;</span><br><span class="line">	<span class="comment">//此处故意为空</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> com.lun.hellomybatisplus.model.User;</span><br><span class="line"><span class="keyword">import</span> com.lun.hellomybatisplus.mapper.UserMapper;</span><br><span class="line"><span class="keyword">import</span> com.lun.hellomybatisplus.service.UserService;</span><br><span class="line"><span class="keyword">import</span> com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;</span><br><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.Autowired;</span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Service;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserServiceImpl</span> <span class="keyword">extends</span> <span class="title class_">ServiceImpl</span>&lt;UserMapper,User&gt; <span class="keyword">implements</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">	<span class="comment">//此处故意为空</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>与下一节联合在一起</p>
<h2 id="67、数据访问-CRUD实验-分页数据展示"><a href="#67、数据访问-CRUD实验-分页数据展示" class="headerlink" title="67、数据访问-CRUD实验-分页数据展示"></a>67、数据访问-CRUD实验-分页数据展示</h2><p>与下一节联合在一起</p>
<h2 id="68、数据访问-CRUD实验-删除用户完成"><a href="#68、数据访问-CRUD实验-删除用户完成" class="headerlink" title="68、数据访问-CRUD实验-删除用户完成"></a>68、数据访问-CRUD实验-删除用户完成</h2><p>添加分页插件：</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyBatisConfig</span> &#123;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * MybatisPlusInterceptor</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> MybatisPlusInterceptor <span class="title function_">paginationInterceptor</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">MybatisPlusInterceptor</span> <span class="variable">mybatisPlusInterceptor</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MybatisPlusInterceptor</span>();</span><br><span class="line">        <span class="comment">// 设置请求的页面大于最大页后操作， true调回到首页，false 继续请求  默认false</span></span><br><span class="line">        <span class="comment">// paginationInterceptor.setOverflow(false);</span></span><br><span class="line">        <span class="comment">// 设置最大单页限制数量，默认 500 条，-1 不受限制</span></span><br><span class="line">        <span class="comment">// paginationInterceptor.setLimit(500);</span></span><br><span class="line">        <span class="comment">// 开启 count 的 join 优化,只针对部分 left join</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">//这是分页拦截器</span></span><br><span class="line">        <span class="type">PaginationInnerInterceptor</span> <span class="variable">paginationInnerInterceptor</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">PaginationInnerInterceptor</span>();</span><br><span class="line">        paginationInnerInterceptor.setOverflow(<span class="literal">true</span>);</span><br><span class="line">        paginationInnerInterceptor.setMaxLimit(<span class="number">500L</span>);</span><br><span class="line">        mybatisPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> mybatisPlusInterceptor;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">table</span> <span class="attr">class</span>=<span class="string">&quot;display table table-bordered table-striped&quot;</span> <span class="attr">id</span>=<span class="string">&quot;dynamic-table&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">thead</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">tr</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">th</span>&gt;</span>#<span class="tag">&lt;/<span class="name">th</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">th</span>&gt;</span>name<span class="tag">&lt;/<span class="name">th</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">th</span>&gt;</span>age<span class="tag">&lt;/<span class="name">th</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">th</span>&gt;</span>email<span class="tag">&lt;/<span class="name">th</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">th</span>&gt;</span>操作<span class="tag">&lt;/<span class="name">th</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">tr</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">thead</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">tbody</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">tr</span> <span class="attr">class</span>=<span class="string">&quot;gradeX&quot;</span> <span class="attr">th:each</span>=<span class="string">&quot;user: $&#123;users.records&#125;&quot;</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">td</span> <span class="attr">th:text</span>=<span class="string">&quot;$&#123;user.id&#125;&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">td</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">td</span>&gt;</span>[[$&#123;user.name&#125;]]<span class="tag">&lt;/<span class="name">td</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">td</span> <span class="attr">th:text</span>=<span class="string">&quot;$&#123;user.age&#125;&quot;</span>&gt;</span>Win 95+<span class="tag">&lt;/<span class="name">td</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">td</span> <span class="attr">th:text</span>=<span class="string">&quot;$&#123;user.email&#125;&quot;</span>&gt;</span>4<span class="tag">&lt;/<span class="name">td</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">td</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">a</span> <span class="attr">th:href</span>=<span class="string">&quot;@&#123;/user/delete/&#123;id&#125;(id=$&#123;user.id&#125;,pn=$&#123;users.current&#125;)&#125;&quot;</span> </span></span><br><span class="line"><span class="tag">                   <span class="attr">class</span>=<span class="string">&quot;btn btn-danger btn-sm&quot;</span> <span class="attr">type</span>=<span class="string">&quot;button&quot;</span>&gt;</span>删除<span class="tag">&lt;/<span class="name">a</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">td</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">tr</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">tfoot</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">table</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;row-fluid&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;span6&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;dataTables_info&quot;</span> <span class="attr">id</span>=<span class="string">&quot;dynamic-table_info&quot;</span>&gt;</span></span><br><span class="line">            当前第[[$&#123;users.current&#125;]]页  总计 [[$&#123;users.pages&#125;]]页  共[[$&#123;users.total&#125;]]条记录</span><br><span class="line">        <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;span6&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;dataTables_paginate paging_bootstrap pagination&quot;</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">ul</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">li</span> <span class="attr">class</span>=<span class="string">&quot;prev disabled&quot;</span>&gt;</span><span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;#&quot;</span>&gt;</span>← 前一页<span class="tag">&lt;/<span class="name">a</span>&gt;</span><span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">li</span> <span class="attr">th:class</span>=<span class="string">&quot;$&#123;num == users.current?&#x27;active&#x27;:&#x27;&#x27;&#125;&quot;</span> </span></span><br><span class="line"><span class="tag">                    <span class="attr">th:each</span>=<span class="string">&quot;num:$&#123;#numbers.sequence(1,users.pages)&#125;&quot;</span> &gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">a</span> <span class="attr">th:href</span>=<span class="string">&quot;@&#123;/dynamic_table(pn=$&#123;num&#125;)&#125;&quot;</span>&gt;</span>[[$&#123;num&#125;]]<span class="tag">&lt;/<span class="name">a</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">li</span> <span class="attr">class</span>=<span class="string">&quot;next disabled&quot;</span>&gt;</span><span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;#&quot;</span>&gt;</span>下一页 → <span class="tag">&lt;/<span class="name">a</span>&gt;</span><span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p><code>#numbers</code>表示methods for formatting numeric objects.<a target="_blank" rel="noopener" href="https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#expression-utility-objects">link</a></p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@GetMapping(&quot;/user/delete/&#123;id&#125;&quot;)</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">deleteUser</span><span class="params">(<span class="meta">@PathVariable(&quot;id&quot;)</span> Long id,</span></span><br><span class="line"><span class="params">                         <span class="meta">@RequestParam(value = &quot;pn&quot;,defaultValue = &quot;1&quot;)</span>Integer pn,</span></span><br><span class="line"><span class="params">                         RedirectAttributes ra)</span>&#123;</span><br><span class="line"></span><br><span class="line">    userService.removeById(id);</span><br><span class="line"></span><br><span class="line">    ra.addAttribute(<span class="string">&quot;pn&quot;</span>,pn);</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;redirect:/dynamic_table&quot;</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@GetMapping(&quot;/dynamic_table&quot;)</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">dynamic_table</span><span class="params">(<span class="meta">@RequestParam(value=&quot;pn&quot;,defaultValue = &quot;1&quot;)</span> Integer pn,Model model)</span>&#123;</span><br><span class="line">    <span class="comment">//表格内容的遍历</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">//从数据库中查出user表中的用户进行展示</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">//构造分页参数</span></span><br><span class="line">    Page&lt;User&gt; page = <span class="keyword">new</span> <span class="title class_">Page</span>&lt;&gt;(pn, <span class="number">2</span>);</span><br><span class="line">    <span class="comment">//调用page进行分页</span></span><br><span class="line">    Page&lt;User&gt; userPage = userService.page(page, <span class="literal">null</span>);</span><br><span class="line"></span><br><span class="line">    model.addAttribute(<span class="string">&quot;users&quot;</span>,userPage);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;table/dynamic_table&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="69、数据访问-准备阿里云Redis环境"><a href="#69、数据访问-准备阿里云Redis环境" class="headerlink" title="69、数据访问-准备阿里云Redis环境"></a>69、数据访问-准备阿里云Redis环境</h2><p><strong>添加依赖</strong>：</p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-data-redis<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">&lt;!--导入jedis--&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>redis.clients<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>jedis<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure>

<ul>
<li><code>RedisAutoConfiguration</code>自动配置类，RedisProperties 属性类 –&gt; spring.redis.xxx是对redis的配置。</li>
<li>连接工厂<code>LettuceConnectionConfiguration</code>、<code>JedisConnectionConfiguration</code>是准备好的。</li>
<li>自动注入了<code>RedisTemplate&lt;Object, Object&gt;</code>，<code>xxxTemplate</code>。</li>
<li>自动注入了<code>StringRedisTemplate</code>，key，value都是String</li>
<li>底层只要我们使用<code>StringRedisTemplate</code>、<code>RedisTemplate</code>就可以操作Redis。</li>
</ul>
<p><strong>外网Redis环境搭建</strong>：</p>
<ol>
<li><p>阿里云按量付费Redis，其中选择<strong>经典网络</strong>。</p>
</li>
<li><p>申请Redis的公网连接地址。</p>
</li>
<li><p>修改白名单，允许<code>0.0.0.0/0</code>访问。</p>
</li>
</ol>
<h2 id="70、数据访问-Redis操作与统计小实验"><a href="#70、数据访问-Redis操作与统计小实验" class="headerlink" title="70、数据访问-Redis操作与统计小实验"></a>70、数据访问-Redis操作与统计小实验</h2><p>相关Redis配置：</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">spring:</span><br><span class="line">  redis:</span><br><span class="line">#   url: redis:<span class="comment">//lfy:Lfy123456@r-bp1nc7reqesxisgxpipd.redis.rds.aliyuncs.com:6379</span></span><br><span class="line">    host: r-bp1nc7reqesxisgxpipd.redis.rds.aliyuncs.com</span><br><span class="line">    port: <span class="number">6379</span></span><br><span class="line">    password: lfy:Lfy123456</span><br><span class="line">    client-type: jedis</span><br><span class="line">    jedis:</span><br><span class="line">      pool:</span><br><span class="line">        max-active: <span class="number">10</span></span><br><span class="line">#   lettuce:# 另一个用来连接redis的java框架</span><br><span class="line">#      pool:</span><br><span class="line">#        max-active: <span class="number">10</span></span><br><span class="line">#        min-idle: <span class="number">5</span></span><br></pre></td></tr></table></figure>

<p>测试Redis连接：</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Boot05WebAdminApplicationTests</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    StringRedisTemplate redisTemplate;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    RedisConnectionFactory redisConnectionFactory;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">testRedis</span><span class="params">()</span>&#123;</span><br><span class="line">        ValueOperations&lt;String, String&gt; operations = redisTemplate.opsForValue();</span><br><span class="line"></span><br><span class="line">        operations.set(<span class="string">&quot;hello&quot;</span>,<span class="string">&quot;world&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="type">String</span> <span class="variable">hello</span> <span class="operator">=</span> operations.get(<span class="string">&quot;hello&quot;</span>);</span><br><span class="line">        System.out.println(hello);</span><br><span class="line"></span><br><span class="line">        System.out.println(redisConnectionFactory.getClass());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>Redis Desktop Manager：可视化Redis管理软件。</p>
<p>URL统计拦截器：</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RedisUrlCountInterceptor</span> <span class="keyword">implements</span> <span class="title class_">HandlerInterceptor</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    StringRedisTemplate redisTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">preHandle</span><span class="params">(HttpServletRequest request, HttpServletResponse response, Object handler)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">uri</span> <span class="operator">=</span> request.getRequestURI();</span><br><span class="line"></span><br><span class="line">        <span class="comment">//默认每次访问当前uri就会计数+1</span></span><br><span class="line">        redisTemplate.opsForValue().increment(uri);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>注册URL统计拦截器：</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AdminWebConfig</span> <span class="keyword">implements</span> <span class="title class_">WebMvcConfigurer</span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    RedisUrlCountInterceptor redisUrlCountInterceptor;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addInterceptors</span><span class="params">(InterceptorRegistry registry)</span> &#123;</span><br><span class="line"></span><br><span class="line">        registry.addInterceptor(redisUrlCountInterceptor)</span><br><span class="line">                .addPathPatterns(<span class="string">&quot;/**&quot;</span>)</span><br><span class="line">                .excludePathPatterns(<span class="string">&quot;/&quot;</span>,<span class="string">&quot;/login&quot;</span>,<span class="string">&quot;/css/**&quot;</span>,<span class="string">&quot;/fonts/**&quot;</span>,<span class="string">&quot;/images/**&quot;</span>,</span><br><span class="line">                        <span class="string">&quot;/js/**&quot;</span>,<span class="string">&quot;/aa/**&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>Filter、Interceptor 几乎拥有相同的功能？</p>
<ul>
<li>Filter是Servlet定义的原生组件，它的好处是脱离Spring应用也能使用。</li>
<li>Interceptor是Spring定义的接口，可以使用Spring的自动装配等功能。</li>
</ul>
<p>调用Redis内的统计数据：</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="meta">@Controller</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">IndexController</span> &#123;</span><br><span class="line"></span><br><span class="line">	<span class="meta">@Autowired</span></span><br><span class="line">    StringRedisTemplate redisTemplate;</span><br><span class="line">    </span><br><span class="line">	<span class="meta">@GetMapping(&quot;/main.html&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">mainPage</span><span class="params">(HttpSession session,Model model)</span>&#123;</span><br><span class="line"></span><br><span class="line">        log.info(<span class="string">&quot;当前方法是：&#123;&#125;&quot;</span>,<span class="string">&quot;mainPage&quot;</span>);</span><br><span class="line"></span><br><span class="line">        ValueOperations&lt;String, String&gt; opsForValue =</span><br><span class="line">                redisTemplate.opsForValue();</span><br><span class="line"></span><br><span class="line">        <span class="type">String</span> <span class="variable">s</span> <span class="operator">=</span> opsForValue.get(<span class="string">&quot;/main.html&quot;</span>);</span><br><span class="line">        <span class="type">String</span> <span class="variable">s1</span> <span class="operator">=</span> opsForValue.get(<span class="string">&quot;/sql&quot;</span>);</span><br><span class="line"></span><br><span class="line">        model.addAttribute(<span class="string">&quot;mainCount&quot;</span>,s);</span><br><span class="line">        model.addAttribute(<span class="string">&quot;sqlCount&quot;</span>,s1);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;main&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h2 id="71、单元测试-JUnit5简介"><a href="#71、单元测试-JUnit5简介" class="headerlink" title="71、单元测试-JUnit5简介"></a>71、单元测试-JUnit5简介</h2><p><strong>Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库</strong></p>
<p><a target="_blank" rel="noopener" href="https://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations">JUnit 5官方文档</a></p>
<p>作为最新版本的JUnit框架，JUnit5与之前版本的JUnit框架有很大的不同。由三个不同子项目的几个不同模块组成。</p>
<p><strong>JUnit 5 &#x3D; JUnit Platform + JUnit Jupiter + JUnit Vintage</strong></p>
<ul>
<li><p><strong>JUnit Platform</strong>: Junit Platform是在JVM上启动测试框架的基础，不仅支持Junit自制的测试引擎，其他测试引擎也都可以接入。</p>
</li>
<li><p><strong>JUnit Jupiter</strong>: JUnit Jupiter提供了JUnit5的新的编程模型，是JUnit5新特性的核心。内部包含了一个<strong>测试引擎</strong>，用于在Junit Platform上运行。</p>
</li>
<li><p><strong>JUnit Vintage</strong>: 由于JUint已经发展多年，为了照顾老的项目，JUnit Vintage提供了兼容JUnit4.x，JUnit3.x的测试引擎。</p>
</li>
</ul>
<p><strong>注意</strong>：</p>
<ul>
<li><p>SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖。如果需要兼容JUnit4需要自行引入（不能使用JUnit4的功能 @Test）</p>
</li>
<li><p>JUnit 5’s Vintage已经从<code>spring-boot-starter-test</code>从移除。如果需要继续兼容Junit4需要自行引入Vintage依赖：</p>
</li>
</ul>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.junit.vintage<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>junit-vintage-engine<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">scope</span>&gt;</span>test<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">exclusions</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">exclusion</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.hamcrest<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>hamcrest-core<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">exclusion</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">exclusions</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure>

<ul>
<li>使用添加JUnit 5，添加对应的starter：</li>
</ul>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-test<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">scope</span>&gt;</span>test<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure>

<ul>
<li>Spring的JUnit 5的基本单元测试模板（Spring的JUnit4的是<code>@SpringBootTest</code>+<code>@RunWith(SpringRunner.class)</code>）：</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> org.junit.jupiter.api.Assertions;</span><br><span class="line"><span class="keyword">import</span> org.junit.jupiter.api.Test;<span class="comment">//注意不是org.junit.Test（这是JUnit4版本的）</span></span><br><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.Autowired;</span><br><span class="line"><span class="keyword">import</span> org.springframework.boot.test.context.SpringBootTest;</span><br><span class="line"></span><br><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">SpringBootApplicationTests</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> Component component;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="comment">//@Transactional 标注后连接数据库有回滚功能</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">contextLoads</span><span class="params">()</span> &#123;</span><br><span class="line">		Assertions.assertEquals(<span class="number">5</span>, component.getFive());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<blockquote>
<p>Jupiter</p>
<p>英 [ˈdʒuːpɪtə(r)]   美 [ˈdʒuːpɪtər]</p>
<p>n. 木星(太阳系中最大的行星)</p>
</blockquote>
<blockquote>
<p>vintage</p>
<p>英 [ˈvɪntɪdʒ]   美 [ˈvɪntɪdʒ]</p>
<p>n. 特定年份(或地方)酿制的酒;酿造年份;采摘葡萄酿酒的期间(或季节);葡萄收获期(或季节)</p>
<p>adj. (指葡萄酒)优质的，上等的，佳酿的;古色古香的(指1917–1930年间制造，车型和品味受人青睐的);(过去某个时期)典型的，优质的;(某人的)最佳作品的</p>
</blockquote>
<h2 id="72、单元测试-常用测试注解"><a href="#72、单元测试-常用测试注解" class="headerlink" title="72、单元测试-常用测试注解"></a>72、单元测试-常用测试注解</h2><p><a target="_blank" rel="noopener" href="https://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations">官方文档 - Annotations</a></p>
<ul>
<li><strong>@Test</strong>：表示方法是测试方法。但是与JUnit4的@Test不同，他的职责非常单一不能声明任何属性，拓展的测试将会由Jupiter提供额外测试</li>
<li><strong>@ParameterizedTest</strong>：表示方法是参数化测试。</li>
<li><strong>@RepeatedTest</strong>：表示方法可重复执行。</li>
<li><strong>@DisplayName</strong>：为测试类或者测试方法设置展示名称。</li>
<li><strong>@BeforeEach</strong>：表示在<strong>每个</strong>单元测试<strong>之前</strong>执行。</li>
<li><strong>@AfterEach</strong>：表示在<strong>每个</strong>单元测试<strong>之后</strong>执行。</li>
<li><strong>@BeforeAll</strong>：表示在<strong>所有</strong>单元测试<strong>之前</strong>执行。</li>
<li><strong>@AfterAll</strong>：表示在<strong>所有</strong>单元测试<strong>之后</strong>执行。</li>
<li><strong>@Tag</strong>：表示单元测试类别，类似于JUnit4中的@Categories。</li>
<li><strong>@Disabled</strong>：表示测试类或测试方法不执行，类似于JUnit4中的@Ignore。</li>
<li><strong>@Timeout</strong>：表示测试方法运行如果超过了指定时间将会返回错误。</li>
<li><strong>@ExtendWith</strong>：为测试类或测试方法提供扩展类引用。</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> org.junit.jupiter.api.*;</span><br><span class="line"></span><br><span class="line"><span class="meta">@DisplayName(&quot;junit5功能测试类&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Junit5Test</span> &#123;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="meta">@DisplayName(&quot;测试displayname注解&quot;)</span></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">testDisplayName</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="number">1</span>);</span><br><span class="line">        System.out.println(jdbcTemplate);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@ParameterizedTest</span></span><br><span class="line">    <span class="meta">@ValueSource(strings = &#123; &quot;racecar&quot;, &quot;radar&quot;, &quot;able was I ere I saw elba&quot; &#125;)</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">palindromes</span><span class="params">(String candidate)</span> &#123;</span><br><span class="line">        assertTrue(StringUtils.isPalindrome(candidate));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line"></span><br><span class="line">    <span class="meta">@Disabled</span></span><br><span class="line">    <span class="meta">@DisplayName(&quot;测试方法2&quot;)</span></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">test2</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="number">2</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@RepeatedTest(5)</span></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">test3</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="number">5</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 规定方法超时时间。超出时间测试出异常</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@throws</span> InterruptedException</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Timeout(value = 500, unit = TimeUnit.MILLISECONDS)</span></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">testTimeout</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        Thread.sleep(<span class="number">600</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="meta">@BeforeEach</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">testBeforeEach</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;测试就要开始了...&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@AfterEach</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">testAfterEach</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;测试结束了...&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@BeforeAll</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">testBeforeAll</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;所有测试就要开始了...&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@AfterAll</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">testAfterAll</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;所有测试以及结束了...&quot;</span>);</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h2 id="73、单元测试-断言机制"><a href="#73、单元测试-断言机制" class="headerlink" title="73、单元测试-断言机制"></a>73、单元测试-断言机制</h2><p>断言Assertion是测试方法中的核心部分，用来对测试需要满足的条件进行验证。这些断言方法都是org.junit.jupiter.api.Assertions的静态方法。检查业务逻辑返回的数据是否合理。所有的测试运行结束以后，会有一个详细的测试报告。</p>
<p>JUnit 5 内置的断言可以分成如下几个类别：</p>
<h3 id="简单断言"><a href="#简单断言" class="headerlink" title="简单断言"></a>简单断言</h3><p>用来对单个值进行简单的验证。如：</p>
<table>
<thead>
<tr>
<th>方法</th>
<th>说明</th>
</tr>
</thead>
<tbody><tr>
<td>assertEquals</td>
<td>判断两个对象或两个原始类型是否相等</td>
</tr>
<tr>
<td>assertNotEquals</td>
<td>判断两个对象或两个原始类型是否不相等</td>
</tr>
<tr>
<td>assertSame</td>
<td>判断两个对象引用是否指向同一个对象</td>
</tr>
<tr>
<td>assertNotSame</td>
<td>判断两个对象引用是否指向不同的对象</td>
</tr>
<tr>
<td>assertTrue</td>
<td>判断给定的布尔值是否为 true</td>
</tr>
<tr>
<td>assertFalse</td>
<td>判断给定的布尔值是否为 false</td>
</tr>
<tr>
<td>assertNull</td>
<td>判断给定的对象引用是否为 null</td>
</tr>
<tr>
<td>assertNotNull</td>
<td>判断给定的对象引用是否不为 null</td>
</tr>
</tbody></table>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="meta">@DisplayName(&quot;simple assertion&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">simple</span><span class="params">()</span> &#123;</span><br><span class="line">     assertEquals(<span class="number">3</span>, <span class="number">1</span> + <span class="number">2</span>, <span class="string">&quot;simple math&quot;</span>);</span><br><span class="line">     assertNotEquals(<span class="number">3</span>, <span class="number">1</span> + <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line">     assertNotSame(<span class="keyword">new</span> <span class="title class_">Object</span>(), <span class="keyword">new</span> <span class="title class_">Object</span>());</span><br><span class="line">     <span class="type">Object</span> <span class="variable">obj</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br><span class="line">     assertSame(obj, obj);</span><br><span class="line"></span><br><span class="line">     assertFalse(<span class="number">1</span> &gt; <span class="number">2</span>);</span><br><span class="line">     assertTrue(<span class="number">1</span> &lt; <span class="number">2</span>);</span><br><span class="line"></span><br><span class="line">     assertNull(<span class="literal">null</span>);</span><br><span class="line">     assertNotNull(<span class="keyword">new</span> <span class="title class_">Object</span>());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="数组断言"><a href="#数组断言" class="headerlink" title="数组断言"></a>数组断言</h3><p>通过 assertArrayEquals 方法来判断两个对象或原始类型的数组是否相等。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="meta">@DisplayName(&quot;array assertion&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">array</span><span class="params">()</span> &#123;</span><br><span class="line">	assertArrayEquals(<span class="keyword">new</span> <span class="title class_">int</span>[]&#123;<span class="number">1</span>, <span class="number">2</span>&#125;, <span class="keyword">new</span> <span class="title class_">int</span>[] &#123;<span class="number">1</span>, <span class="number">2</span>&#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="组合断言"><a href="#组合断言" class="headerlink" title="组合断言"></a>组合断言</h3><p><code>assertAll()</code>方法接受多个 <code>org.junit.jupiter.api.Executable</code> 函数式接口的实例作为要验证的断言，可以通过 lambda 表达式很容易的提供这些断言。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="meta">@DisplayName(&quot;assert all&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">all</span><span class="params">()</span> &#123;</span><br><span class="line"> assertAll(<span class="string">&quot;Math&quot;</span>,</span><br><span class="line">    () -&gt; assertEquals(<span class="number">2</span>, <span class="number">1</span> + <span class="number">1</span>),</span><br><span class="line">    () -&gt; assertTrue(<span class="number">1</span> &gt; <span class="number">0</span>)</span><br><span class="line"> );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="异常断言"><a href="#异常断言" class="headerlink" title="异常断言"></a>异常断言</h3><p>在JUnit4时期，想要测试方法的异常情况时，需要用<code>@Rule</code>注解的<code>ExpectedException</code>变量还是比较麻烦的。而JUnit5提供了一种新的断言方式<code>Assertions.assertThrows()</code>，配合函数式编程就可以进行使用。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="meta">@DisplayName(&quot;异常测试&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">exceptionTest</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">ArithmeticException</span> <span class="variable">exception</span> <span class="operator">=</span> Assertions.assertThrows(</span><br><span class="line">           <span class="comment">//扔出断言异常</span></span><br><span class="line">            ArithmeticException.class, () -&gt; System.out.println(<span class="number">1</span> % <span class="number">0</span>));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="超时断言"><a href="#超时断言" class="headerlink" title="超时断言"></a>超时断言</h3><p>JUnit5还提供了Assertions.assertTimeout()为测试方法设置了超时时间。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="meta">@DisplayName(&quot;超时测试&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">timeoutTest</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">//如果测试方法时间超过1s将会异常</span></span><br><span class="line">    Assertions.assertTimeout(Duration.ofMillis(<span class="number">1000</span>), () -&gt; Thread.sleep(<span class="number">500</span>));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="快速失败"><a href="#快速失败" class="headerlink" title="快速失败"></a>快速失败</h3><p>通过 fail 方法直接使得测试失败。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="meta">@DisplayName(&quot;fail&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">shouldFail</span><span class="params">()</span> &#123;</span><br><span class="line">	fail(<span class="string">&quot;This should fail&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h2 id="74、单元测试-前置条件"><a href="#74、单元测试-前置条件" class="headerlink" title="74、单元测试-前置条件"></a>74、单元测试-前置条件</h2><p>Unit 5 中的前置条件（assumptions【假设】）类似于断言，不同之处在于不满足的<strong>断言assertions</strong>会使得测试方法失败，而<strong>不满足的前置条件只会使得测试方法的执行终止</strong>。</p>
<p>前置条件可以看成是测试方法执行的前提，当该前提不满足时，就没有继续执行的必要。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@DisplayName(&quot;前置条件&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AssumptionsTest</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">environment</span> <span class="operator">=</span> <span class="string">&quot;DEV&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="meta">@DisplayName(&quot;simple&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">simpleAssume</span><span class="params">()</span> &#123;</span><br><span class="line">        assumeTrue(Objects.equals(<span class="built_in">this</span>.environment, <span class="string">&quot;DEV&quot;</span>));</span><br><span class="line">        assumeFalse(() -&gt; Objects.equals(<span class="built_in">this</span>.environment, <span class="string">&quot;PROD&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="meta">@DisplayName(&quot;assume then do&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">assumeThenDo</span><span class="params">()</span> &#123;</span><br><span class="line">        assumingThat(</span><br><span class="line">            Objects.equals(<span class="built_in">this</span>.environment, <span class="string">&quot;DEV&quot;</span>),</span><br><span class="line">            () -&gt; System.out.println(<span class="string">&quot;In DEV&quot;</span>)</span><br><span class="line">        );</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>assumeTrue</code> 和 <code>assumFalse</code> 确保给定的条件为 <code>true</code> 或 <code>false</code>，不满足条件会使得测试执行终止。</p>
<p><code>assumingThat</code> 的参数是表示条件的布尔值和对应的 Executable 接口的实现对象。只有条件满足时，<code>Executable</code> 对象才会被执行；当条件不满足时，测试执行并不会终止。</p>
<h2 id="75、单元测试-嵌套测试"><a href="#75、单元测试-嵌套测试" class="headerlink" title="75、单元测试-嵌套测试"></a>75、单元测试-嵌套测试</h2><p><a target="_blank" rel="noopener" href="https://junit.org/junit5/docs/current/user-guide/#writing-tests-nested">官方文档 - Nested Tests</a></p>
<p>JUnit 5 可以通过 Java 中的内部类和<code>@Nested</code> 注解实现嵌套测试，从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用<code>@BeforeEach</code> 和<code>@AfterEach</code>注解，而且嵌套的层次没有限制。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@DisplayName(&quot;A stack&quot;)</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">TestingAStackDemo</span> &#123;</span><br><span class="line"></span><br><span class="line">    Stack&lt;Object&gt; stack;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="meta">@DisplayName(&quot;is instantiated with new Stack()&quot;)</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">isInstantiatedWithNew</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">Stack</span>&lt;&gt;();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Nested</span></span><br><span class="line">    <span class="meta">@DisplayName(&quot;when new&quot;)</span></span><br><span class="line">    <span class="keyword">class</span> <span class="title class_">WhenNew</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@BeforeEach</span></span><br><span class="line">        <span class="keyword">void</span> <span class="title function_">createNewStack</span><span class="params">()</span> &#123;</span><br><span class="line">            stack = <span class="keyword">new</span> <span class="title class_">Stack</span>&lt;&gt;();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Test</span></span><br><span class="line">        <span class="meta">@DisplayName(&quot;is empty&quot;)</span></span><br><span class="line">        <span class="keyword">void</span> <span class="title function_">isEmpty</span><span class="params">()</span> &#123;</span><br><span class="line">            assertTrue(stack.isEmpty());</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Test</span></span><br><span class="line">        <span class="meta">@DisplayName(&quot;throws EmptyStackException when popped&quot;)</span></span><br><span class="line">        <span class="keyword">void</span> <span class="title function_">throwsExceptionWhenPopped</span><span class="params">()</span> &#123;</span><br><span class="line">            assertThrows(EmptyStackException.class, stack::pop);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Test</span></span><br><span class="line">        <span class="meta">@DisplayName(&quot;throws EmptyStackException when peeked&quot;)</span></span><br><span class="line">        <span class="keyword">void</span> <span class="title function_">throwsExceptionWhenPeeked</span><span class="params">()</span> &#123;</span><br><span class="line">            assertThrows(EmptyStackException.class, stack::peek);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Nested</span></span><br><span class="line">        <span class="meta">@DisplayName(&quot;after pushing an element&quot;)</span></span><br><span class="line">        <span class="keyword">class</span> <span class="title class_">AfterPushing</span> &#123;</span><br><span class="line"></span><br><span class="line">            <span class="type">String</span> <span class="variable">anElement</span> <span class="operator">=</span> <span class="string">&quot;an element&quot;</span>;</span><br><span class="line"></span><br><span class="line">            <span class="meta">@BeforeEach</span></span><br><span class="line">            <span class="keyword">void</span> <span class="title function_">pushAnElement</span><span class="params">()</span> &#123;</span><br><span class="line">                stack.push(anElement);</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="meta">@Test</span></span><br><span class="line">            <span class="meta">@DisplayName(&quot;it is no longer empty&quot;)</span></span><br><span class="line">            <span class="keyword">void</span> <span class="title function_">isNotEmpty</span><span class="params">()</span> &#123;</span><br><span class="line">                assertFalse(stack.isEmpty());</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="meta">@Test</span></span><br><span class="line">            <span class="meta">@DisplayName(&quot;returns the element when popped and is empty&quot;)</span></span><br><span class="line">            <span class="keyword">void</span> <span class="title function_">returnElementWhenPopped</span><span class="params">()</span> &#123;</span><br><span class="line">                assertEquals(anElement, stack.pop());</span><br><span class="line">                assertTrue(stack.isEmpty());</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="meta">@Test</span></span><br><span class="line">            <span class="meta">@DisplayName(&quot;returns the element when peeked but remains not empty&quot;)</span></span><br><span class="line">            <span class="keyword">void</span> <span class="title function_">returnElementWhenPeeked</span><span class="params">()</span> &#123;</span><br><span class="line">                assertEquals(anElement, stack.peek());</span><br><span class="line">                assertFalse(stack.isEmpty());</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h2 id="76、单元测试-参数化测试"><a href="#76、单元测试-参数化测试" class="headerlink" title="76、单元测试-参数化测试"></a>76、单元测试-参数化测试</h2><p><a target="_blank" rel="noopener" href="https://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests">官方文档 - Parameterized Tests</a></p>
<p>参数化测试是JUnit5很重要的一个新特性，它使得用不同的参数多次运行测试成为了可能，也为我们的单元测试带来许多便利。</p>
<p>利用@ValueSource等注解，指定入参，我们将可以使用不同的参数进行多次单元测试，而不需要每新增一个参数就新增一个单元测试，省去了很多冗余代码。</p>
<p>利用**@ValueSource**等注解，指定入参，我们将可以使用不同的参数进行多次单元测试，而不需要每新增一个参数就新增一个单元测试，省去了很多冗余代码。</p>
<ul>
<li><strong>@ValueSource</strong>: 为参数化测试指定入参来源，支持八大基础类以及String类型,Class类型</li>
<li><strong>@NullSource</strong>: 表示为参数化测试提供一个null的入参</li>
<li><strong>@EnumSource</strong>: 表示为参数化测试提供一个枚举入参</li>
<li><strong>@CsvFileSource</strong>：表示读取指定CSV文件内容作为参数化测试入参</li>
<li><strong>@MethodSource</strong>：表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)</li>
</ul>
<p>当然如果参数化测试仅仅只能做到指定普通的入参还达不到让我觉得惊艳的地步。让我真正感到他的强大之处的地方在于他可以支持外部的各类入参。如:CSV,YML,JSON 文件甚至方法的返回值也可以作为入参。只需要去实现**<code>ArgumentsProvider</code>**接口，任何外部文件都可以作为它的入参。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@ParameterizedTest</span></span><br><span class="line"><span class="meta">@ValueSource(strings = &#123;&quot;one&quot;, &quot;two&quot;, &quot;three&quot;&#125;)</span></span><br><span class="line"><span class="meta">@DisplayName(&quot;参数化测试1&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">parameterizedTest1</span><span class="params">(String string)</span> &#123;</span><br><span class="line">    System.out.println(string);</span><br><span class="line">    Assertions.assertTrue(StringUtils.isNotBlank(string));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@ParameterizedTest</span></span><br><span class="line"><span class="meta">@MethodSource(&quot;method&quot;)</span>    <span class="comment">//指定方法名</span></span><br><span class="line"><span class="meta">@DisplayName(&quot;方法来源参数&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">testWithExplicitLocalMethodSource</span><span class="params">(String name)</span> &#123;</span><br><span class="line">    System.out.println(name);</span><br><span class="line">    Assertions.assertNotNull(name);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> Stream&lt;String&gt; <span class="title function_">method</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> Stream.of(<span class="string">&quot;apple&quot;</span>, <span class="string">&quot;banana&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="迁移指南"><a href="#迁移指南" class="headerlink" title="迁移指南"></a>迁移指南</h3><p><a target="_blank" rel="noopener" href="https://junit.org/junit5/docs/current/user-guide/#migrating-from-junit4">官方文档 - Migrating from JUnit 4</a></p>
<p>在进行迁移的时候需要注意如下的变化：</p>
<ul>
<li>注解在 <code>org.junit.jupiter.api</code> 包中，断言在 <code>org.junit.jupiter.api.Assertions</code> 类中，前置条件在 <code>org.junit.jupiter.api.Assumptions</code> 类中。</li>
<li>把<code>@Before</code> 和<code>@After</code> 替换成<code>@BeforeEach</code> 和<code>@AfterEach</code>。</li>
<li>把<code>@BeforeClass</code> 和<code>@AfterClass</code> 替换成<code>@BeforeAll</code> 和@AfterAll。</li>
<li>把<code>@Ignore</code> 替换成<code>@Disabled</code>。</li>
<li>把<code>@Category</code> 替换成<code>@Tag</code>。</li>
<li>把<code>@RunWith</code>、<code>@Rule</code> 和<code>@ClassRule</code> 替换成<code>@ExtendWith</code>。</li>
</ul>
<h2 id="77、指标监控-SpringBoot-Actuator与Endpoint"><a href="#77、指标监控-SpringBoot-Actuator与Endpoint" class="headerlink" title="77、指标监控-SpringBoot Actuator与Endpoint"></a>77、指标监控-SpringBoot Actuator与Endpoint</h2><p>未来每一个微服务在云上部署以后，我们都需要对其进行监控、追踪、审计、控制等。SpringBoot就抽取了Actuator场景，使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能。</p>
<p><a target="_blank" rel="noopener" href="https://docs.spring.io/spring-boot/docs/2.4.2/reference/htmlsingle/#production-ready">官方文档 - Spring Boot Actuator: Production-ready Features</a></p>
<p><strong>1.x与2.x的不同</strong>：</p>
<ul>
<li><p>SpringBoot Actuator 1.x</p>
<ul>
<li>支持SpringMVC</li>
<li>基于继承方式进行扩展</li>
<li>层级Metrics配置</li>
<li>自定义Metrics收集</li>
<li>默认较少的安全策略</li>
</ul>
</li>
<li><p>SpringBoot Actuator 2.x</p>
<ul>
<li>支持SpringMVC、JAX-RS以及Webflux</li>
<li>注解驱动进行扩展</li>
<li>层级&amp;名称空间Metrics</li>
<li>底层使用MicroMeter，强大、便捷默认丰富的安全策略</li>
</ul>
</li>
</ul>
<h3 id="如何使用"><a href="#如何使用" class="headerlink" title="如何使用"></a>如何使用</h3><ul>
<li>添加依赖：</li>
</ul>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-actuator<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure>

<ul>
<li>访问<code>http://localhost:8080/actuator/**</code>。</li>
<li>暴露所有监控信息为HTTP。</li>
</ul>
<figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">management:</span></span><br><span class="line">  <span class="attr">endpoints:</span></span><br><span class="line">    <span class="attr">enabled-by-default:</span> <span class="literal">true</span> <span class="comment">#暴露所有端点信息</span></span><br><span class="line">    <span class="attr">web:</span></span><br><span class="line">      <span class="attr">exposure:</span></span><br><span class="line">        <span class="attr">include:</span> <span class="string">&#x27;*&#x27;</span>  <span class="comment">#以web方式暴露</span></span><br></pre></td></tr></table></figure>

<ul>
<li>测试例子<ul>
<li><a target="_blank" rel="noopener" href="http://localhost:8080/actuator/beans">http://localhost:8080/actuator/beans</a></li>
<li><a target="_blank" rel="noopener" href="http://localhost:8080/actuator/configprops">http://localhost:8080/actuator/configprops</a></li>
<li><a target="_blank" rel="noopener" href="http://localhost:8080/actuator/metrics">http://localhost:8080/actuator/metrics</a></li>
<li><a target="_blank" rel="noopener" href="http://localhost:8080/actuator/metrics/jvm.gc.pause">http://localhost:8080/actuator/metrics/jvm.gc.pause</a></li>
<li><a target="_blank" rel="noopener" href="http://localhost:8080/actuator/metrics/endpointName/detailPath">http://localhost:8080/actuator/metrics/endpointName/detailPath</a></li>
</ul>
</li>
</ul>
<blockquote>
<p>actuator</p>
<p>英 [ˈæktjʊeɪtə]   美 [ˈæktjuˌeɪtər]</p>
<p>n. 致动（促动，激励，调节）器；传动（装置，机构）；拖动装置；马达；操作机构；执行机构（元件）；（电磁铁）螺线管；操纵装置（阀门）；调速控制器；往复运动油（气）缸；作动筒</p>
</blockquote>
<blockquote>
<p>metric</p>
<p>英 [ˈmetrɪk]   美 [ˈmetrɪk]</p>
<p>adj. 米制的;公制的;按公制制作的;用公制测量的</p>
<p>n. 度量标准;[数学]度量;诗体;韵文;诗韵</p>
</blockquote>
<h2 id="78、指标监控-常使用的端点及开启与禁用"><a href="#78、指标监控-常使用的端点及开启与禁用" class="headerlink" title="78、指标监控-常使用的端点及开启与禁用"></a>78、指标监控-常使用的端点及开启与禁用</h2><h3 id="常使用的端点"><a href="#常使用的端点" class="headerlink" title="常使用的端点"></a>常使用的端点</h3><table>
<thead>
<tr>
<th>ID</th>
<th>描述</th>
</tr>
</thead>
<tbody><tr>
<td><code>auditevents</code></td>
<td>暴露当前应用程序的审核事件信息。需要一个<code>AuditEventRepository组件</code>。</td>
</tr>
<tr>
<td><code>beans</code></td>
<td>显示应用程序中所有Spring Bean的完整列表。</td>
</tr>
<tr>
<td><code>caches</code></td>
<td>暴露可用的缓存。</td>
</tr>
<tr>
<td><code>conditions</code></td>
<td>显示自动配置的所有条件信息，包括匹配或不匹配的原因。</td>
</tr>
<tr>
<td><code>configprops</code></td>
<td>显示所有<code>@ConfigurationProperties</code>。</td>
</tr>
<tr>
<td><code>env</code></td>
<td>暴露Spring的属性<code>ConfigurableEnvironment</code></td>
</tr>
<tr>
<td><code>flyway</code></td>
<td>显示已应用的所有Flyway数据库迁移。 需要一个或多个<code>Flyway</code>组件。</td>
</tr>
<tr>
<td><code>health</code></td>
<td>显示应用程序运行状况信息。</td>
</tr>
<tr>
<td><code>httptrace</code></td>
<td>显示HTTP跟踪信息（默认情况下，最近100个HTTP请求-响应）。需要一个<code>HttpTraceRepository</code>组件。</td>
</tr>
<tr>
<td><code>info</code></td>
<td>显示应用程序信息。</td>
</tr>
<tr>
<td><code>integrationgraph</code></td>
<td>显示Spring <code>integrationgraph</code> 。需要依赖<code>spring-integration-core</code>。</td>
</tr>
<tr>
<td><code>loggers</code></td>
<td>显示和修改应用程序中日志的配置。</td>
</tr>
<tr>
<td><code>liquibase</code></td>
<td>显示已应用的所有Liquibase数据库迁移。需要一个或多个<code>Liquibase</code>组件。</td>
</tr>
<tr>
<td><code>metrics</code></td>
<td>显示当前应用程序的“指标”信息。</td>
</tr>
<tr>
<td><code>mappings</code></td>
<td>显示所有<code>@RequestMapping</code>路径列表。</td>
</tr>
<tr>
<td><code>scheduledtasks</code></td>
<td>显示应用程序中的计划任务。</td>
</tr>
<tr>
<td><code>sessions</code></td>
<td>允许从Spring Session支持的会话存储中检索和删除用户会话。需要使用Spring Session的基于Servlet的Web应用程序。</td>
</tr>
<tr>
<td><code>shutdown</code></td>
<td>使应用程序正常关闭。默认禁用。</td>
</tr>
<tr>
<td><code>startup</code></td>
<td>显示由<code>ApplicationStartup</code>收集的启动步骤数据。需要使用<code>SpringApplication</code>进行配置<code>BufferingApplicationStartup</code>。</td>
</tr>
<tr>
<td><code>threaddump</code></td>
<td>执行线程转储。</td>
</tr>
</tbody></table>
<p>如果您的应用程序是Web应用程序（Spring MVC，Spring WebFlux或Jersey），则可以使用以下附加端点：</p>
<table>
<thead>
<tr>
<th>ID</th>
<th>描述</th>
</tr>
</thead>
<tbody><tr>
<td><code>heapdump</code></td>
<td>返回<code>hprof</code>堆转储文件。</td>
</tr>
<tr>
<td><code>jolokia</code></td>
<td>通过HTTP暴露JMX bean（需要引入Jolokia，不适用于WebFlux）。需要引入依赖<code>jolokia-core</code>。</td>
</tr>
<tr>
<td><code>logfile</code></td>
<td>返回日志文件的内容（如果已设置<code>logging.file.name</code>或<code>logging.file.path</code>属性）。支持使用HTTP<code>Range</code>标头来检索部分日志文件的内容。</td>
</tr>
<tr>
<td><code>prometheus</code></td>
<td>以Prometheus服务器可以抓取的格式公开指标。需要依赖<code>micrometer-registry-prometheus</code>。</td>
</tr>
</tbody></table>
<p>其中最常用的Endpoint：</p>
<ul>
<li><strong>Health：监控状况</strong></li>
<li><strong>Metrics：运行时指标</strong></li>
<li><strong>Loggers：日志记录</strong></li>
</ul>
<h3 id="Health-Endpoint"><a href="#Health-Endpoint" class="headerlink" title="Health Endpoint"></a>Health Endpoint</h3><p>健康检查端点，我们一般用于在云平台，平台会定时的检查应用的健康状况，我们就需要Health Endpoint可以为平台返回当前应用的一系列组件健康状况的集合。</p>
<p>重要的几点：</p>
<ul>
<li>health endpoint返回的结果，应该是一系列健康检查后的一个汇总报告。</li>
<li>很多的健康检查默认已经自动配置好了，比如：数据库、redis等。</li>
<li>可以很容易的添加自定义的健康检查机制。</li>
</ul>
<h3 id="Metrics-Endpoint"><a href="#Metrics-Endpoint" class="headerlink" title="Metrics Endpoint"></a>Metrics Endpoint</h3><p>提供详细的、层级的、空间指标信息，这些信息可以被pull（主动推送）或者push（被动获取）方式得到：</p>
<ul>
<li>通过Metrics对接多种监控系统。</li>
<li>简化核心Metrics开发。</li>
<li>添加自定义Metrics或者扩展已有Metrics。</li>
</ul>
<h3 id="开启与禁用Endpoints"><a href="#开启与禁用Endpoints" class="headerlink" title="开启与禁用Endpoints"></a>开启与禁用Endpoints</h3><ul>
<li>默认所有的Endpoint除过shutdown都是开启的。</li>
<li>需要开启或者禁用某个Endpoint。配置模式为<code>management.endpoint.&lt;endpointName&gt;.enabled = true</code></li>
</ul>
<figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">management:</span></span><br><span class="line">  <span class="attr">endpoint:</span></span><br><span class="line">    <span class="attr">beans:</span></span><br><span class="line">      <span class="attr">enabled:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure>

<ul>
<li>或者禁用所有的Endpoint然后手动开启指定的Endpoint。</li>
</ul>
<figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">management:</span></span><br><span class="line">  <span class="attr">endpoints:</span></span><br><span class="line">    <span class="attr">enabled-by-default:</span> <span class="literal">false</span></span><br><span class="line">  <span class="attr">endpoint:</span></span><br><span class="line">    <span class="attr">beans:</span></span><br><span class="line">      <span class="attr">enabled:</span> <span class="literal">true</span></span><br><span class="line">    <span class="attr">health:</span></span><br><span class="line">      <span class="attr">enabled:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure>



<h3 id="暴露Endpoints"><a href="#暴露Endpoints" class="headerlink" title="暴露Endpoints"></a>暴露Endpoints</h3><p>支持的暴露方式</p>
<ul>
<li>HTTP：默认只暴露health和info。</li>
<li>JMX：默认暴露所有Endpoint。</li>
<li>除过health和info，剩下的Endpoint都应该进行保护访问。如果引入Spring Security，则会默认配置安全访问规则。</li>
</ul>
<table>
<thead>
<tr>
<th align="left">ID</th>
<th align="left">JMX</th>
<th align="left">Web</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><code>auditevents</code></td>
<td align="left">Yes</td>
<td align="left">No</td>
</tr>
<tr>
<td align="left"><code>beans</code></td>
<td align="left">Yes</td>
<td align="left">No</td>
</tr>
<tr>
<td align="left"><code>caches</code></td>
<td align="left">Yes</td>
<td align="left">No</td>
</tr>
<tr>
<td align="left"><code>conditions</code></td>
<td align="left">Yes</td>
<td align="left">No</td>
</tr>
<tr>
<td align="left"><code>configprops</code></td>
<td align="left">Yes</td>
<td align="left">No</td>
</tr>
<tr>
<td align="left"><code>env</code></td>
<td align="left">Yes</td>
<td align="left">No</td>
</tr>
<tr>
<td align="left"><code>flyway</code></td>
<td align="left">Yes</td>
<td align="left">No</td>
</tr>
<tr>
<td align="left"><code>health</code></td>
<td align="left">Yes</td>
<td align="left">Yes</td>
</tr>
<tr>
<td align="left"><code>heapdump</code></td>
<td align="left">N&#x2F;A</td>
<td align="left">No</td>
</tr>
<tr>
<td align="left"><code>httptrace</code></td>
<td align="left">Yes</td>
<td align="left">No</td>
</tr>
<tr>
<td align="left"><code>info</code></td>
<td align="left">Yes</td>
<td align="left">Yes</td>
</tr>
<tr>
<td align="left"><code>integrationgraph</code></td>
<td align="left">Yes</td>
<td align="left">No</td>
</tr>
<tr>
<td align="left"><code>jolokia</code></td>
<td align="left">N&#x2F;A</td>
<td align="left">No</td>
</tr>
<tr>
<td align="left"><code>logfile</code></td>
<td align="left">N&#x2F;A</td>
<td align="left">No</td>
</tr>
<tr>
<td align="left"><code>loggers</code></td>
<td align="left">Yes</td>
<td align="left">No</td>
</tr>
<tr>
<td align="left"><code>liquibase</code></td>
<td align="left">Yes</td>
<td align="left">No</td>
</tr>
<tr>
<td align="left"><code>metrics</code></td>
<td align="left">Yes</td>
<td align="left">No</td>
</tr>
<tr>
<td align="left"><code>mappings</code></td>
<td align="left">Yes</td>
<td align="left">No</td>
</tr>
<tr>
<td align="left"><code>prometheus</code></td>
<td align="left">N&#x2F;A</td>
<td align="left">No</td>
</tr>
<tr>
<td align="left"><code>scheduledtasks</code></td>
<td align="left">Yes</td>
<td align="left">No</td>
</tr>
<tr>
<td align="left"><code>sessions</code></td>
<td align="left">Yes</td>
<td align="left">No</td>
</tr>
<tr>
<td align="left"><code>shutdown</code></td>
<td align="left">Yes</td>
<td align="left">No</td>
</tr>
<tr>
<td align="left"><code>startup</code></td>
<td align="left">Yes</td>
<td align="left">No</td>
</tr>
<tr>
<td align="left"><code>threaddump</code></td>
<td align="left">Yes</td>
<td align="left">No</td>
</tr>
</tbody></table>
<p>若要更改公开的Endpoint，请配置以下的包含和排除属性：</p>
<table>
<thead>
<tr>
<th align="left">Property</th>
<th align="left">Default</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><code>management.endpoints.jmx.exposure.exclude</code></td>
<td align="left"></td>
</tr>
<tr>
<td align="left"><code>management.endpoints.jmx.exposure.include</code></td>
<td align="left"><code>*</code></td>
</tr>
<tr>
<td align="left"><code>management.endpoints.web.exposure.exclude</code></td>
<td align="left"></td>
</tr>
<tr>
<td align="left"><code>management.endpoints.web.exposure.include</code></td>
<td align="left"><code>info, health</code></td>
</tr>
</tbody></table>
<p><a target="_blank" rel="noopener" href="https://docs.spring.io/spring-boot/docs/2.4.2/reference/htmlsingle/#production-ready-endpoints-exposing-endpoints">官方文档 - Exposing Endpoints</a></p>
<h2 id="79、指标监控-定制Endpoint"><a href="#79、指标监控-定制Endpoint" class="headerlink" title="79、指标监控-定制Endpoint"></a>79、指标监控-定制Endpoint</h2><h3 id="定制-Health-信息"><a href="#定制-Health-信息" class="headerlink" title="定制 Health 信息"></a>定制 Health 信息</h3><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">management:</span></span><br><span class="line">    <span class="attr">health:</span></span><br><span class="line">      <span class="attr">enabled:</span> <span class="literal">true</span></span><br><span class="line">      <span class="attr">show-details:</span> <span class="string">always</span> <span class="comment">#总是显示详细信息。可显示每个模块的状态信息</span></span><br></pre></td></tr></table></figure>

<p>通过实现<code>HealthIndicator </code>接口，或继承<code>MyComHealthIndicator </code>类。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> org.springframework.boot.actuate.health.Health;</span><br><span class="line"><span class="keyword">import</span> org.springframework.boot.actuate.health.HealthIndicator;</span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Component;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyHealthIndicator</span> <span class="keyword">implements</span> <span class="title class_">HealthIndicator</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Health <span class="title function_">health</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">errorCode</span> <span class="operator">=</span> check(); <span class="comment">// perform some specific health check</span></span><br><span class="line">        <span class="keyword">if</span> (errorCode != <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> Health.down().withDetail(<span class="string">&quot;Error Code&quot;</span>, errorCode).build();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> Health.up().build();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">构建Health</span></span><br><span class="line"><span class="comment">Health build = Health.down()</span></span><br><span class="line"><span class="comment">                .withDetail(&quot;msg&quot;, &quot;error service&quot;)</span></span><br><span class="line"><span class="comment">                .withDetail(&quot;code&quot;, &quot;500&quot;)</span></span><br><span class="line"><span class="comment">                .withException(new RuntimeException())</span></span><br><span class="line"><span class="comment">                .build();</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyComHealthIndicator</span> <span class="keyword">extends</span> <span class="title class_">AbstractHealthIndicator</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 真实的检查方法</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> builder</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@throws</span> Exception</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">doHealthCheck</span><span class="params">(Health.Builder builder)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="comment">//mongodb。  获取连接进行测试</span></span><br><span class="line">        Map&lt;String,Object&gt; map = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">        <span class="comment">// 检查完成</span></span><br><span class="line">        <span class="keyword">if</span>(<span class="number">1</span> == <span class="number">2</span>)&#123;</span><br><span class="line"><span class="comment">//            builder.up(); //健康</span></span><br><span class="line">            builder.status(Status.UP);</span><br><span class="line">            map.put(<span class="string">&quot;count&quot;</span>,<span class="number">1</span>);</span><br><span class="line">            map.put(<span class="string">&quot;ms&quot;</span>,<span class="number">100</span>);</span><br><span class="line">        &#125;<span class="keyword">else</span> &#123;</span><br><span class="line"><span class="comment">//            builder.down();</span></span><br><span class="line">            builder.status(Status.OUT_OF_SERVICE);</span><br><span class="line">            map.put(<span class="string">&quot;err&quot;</span>,<span class="string">&quot;连接超时&quot;</span>);</span><br><span class="line">            map.put(<span class="string">&quot;ms&quot;</span>,<span class="number">3000</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        builder.withDetail(<span class="string">&quot;code&quot;</span>,<span class="number">100</span>)</span><br><span class="line">                .withDetails(map);</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="定制info信息"><a href="#定制info信息" class="headerlink" title="定制info信息"></a>定制info信息</h3><p>常用两种方式：</p>
<ul>
<li>编写配置文件</li>
</ul>
<figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">info:</span></span><br><span class="line">  <span class="attr">appName:</span> <span class="string">boot-admin</span></span><br><span class="line">  <span class="attr">version:</span> <span class="number">2.0</span><span class="number">.1</span></span><br><span class="line">  <span class="attr">mavenProjectName:</span> <span class="string">@project.artifactId@</span>  <span class="comment">#使用@@可以获取maven的pom文件值</span></span><br><span class="line">  <span class="attr">mavenProjectVersion:</span> <span class="string">@project.version@</span></span><br></pre></td></tr></table></figure>

<ul>
<li>编写InfoContributor</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.Collections;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.springframework.boot.actuate.info.Info;</span><br><span class="line"><span class="keyword">import</span> org.springframework.boot.actuate.info.InfoContributor;</span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Component;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ExampleInfoContributor</span> <span class="keyword">implements</span> <span class="title class_">InfoContributor</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">contribute</span><span class="params">(Info.Builder builder)</span> &#123;</span><br><span class="line">        builder.withDetail(<span class="string">&quot;example&quot;</span>,</span><br><span class="line">                Collections.singletonMap(<span class="string">&quot;key&quot;</span>, <span class="string">&quot;value&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><a target="_blank" rel="noopener" href="http://localhost:8080/actuator/info">http://localhost:8080/actuator/info</a> 会输出以上方式返回的所有info信息</p>
<h3 id="定制Metrics信息"><a href="#定制Metrics信息" class="headerlink" title="定制Metrics信息"></a>定制Metrics信息</h3><p><a target="_blank" rel="noopener" href="https://docs.spring.io/spring-boot/docs/2.4.2/reference/htmlsingle/#production-ready-metrics-meter">Spring Boot支持的metrics</a></p>
<p>增加定制Metrics：</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MyService</span>&#123;</span><br><span class="line">    Counter counter;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">MyService</span><span class="params">(MeterRegistry meterRegistry)</span>&#123;</span><br><span class="line">         counter = meterRegistry.counter(<span class="string">&quot;myservice.method.running.counter&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">hello</span><span class="params">()</span> &#123;</span><br><span class="line">        counter.increment();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//也可以使用下面的方式</span></span><br><span class="line"><span class="meta">@Bean</span></span><br><span class="line">MeterBinder <span class="title function_">queueSize</span><span class="params">(Queue queue)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> (registry) -&gt; Gauge.builder(<span class="string">&quot;queueSize&quot;</span>, queue::size).register(registry);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="定制Endpoint"><a href="#定制Endpoint" class="headerlink" title="定制Endpoint"></a>定制Endpoint</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@Endpoint(id = &quot;container&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DockerEndpoint</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ReadOperation</span></span><br><span class="line">    <span class="keyword">public</span> Map <span class="title function_">getDockerInfo</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="keyword">return</span> Collections.singletonMap(<span class="string">&quot;info&quot;</span>,<span class="string">&quot;docker started...&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@WriteOperation</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">restartDocker</span><span class="params">()</span>&#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;docker restarted....&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>场景：</p>
<ul>
<li>开发ReadinessEndpoint来管理程序是否就绪。</li>
<li>开发LivenessEndpoint来管理程序是否存活。</li>
</ul>
<h2 id="80、指标监控-Boot-Admin-Server"><a href="#80、指标监控-Boot-Admin-Server" class="headerlink" title="80、指标监控-Boot Admin Server"></a>80、指标监控-Boot Admin Server</h2><p><a href="">官方Github</a></p>
<p><a target="_blank" rel="noopener" href="https://codecentric.github.io/spring-boot-admin/2.3.1/#getting-started">官方文档</a></p>
<p>可视化指标监控</p>
<blockquote>
<p>What is Spring Boot Admin?</p>
<p>codecentric’s Spring Boot Admin is a community project to manage and monitor your <a target="_blank" rel="noopener" href="http://projects.spring.io/spring-boot/">Spring Boot</a> ® applications. The applications register with our Spring Boot Admin Client (via HTTP) or are discovered using Spring Cloud ® (e.g. Eureka, Consul). The UI is just a Vue.js application on top of the Spring Boot Actuator endpoints.</p>
</blockquote>
<p><a target="_blank" rel="noopener" href="https://codecentric.github.io/spring-boot-admin/2.3.1/#getting-started">开始使用方法</a></p>
<h2 id="81、高级特性-Profile环境切换"><a href="#81、高级特性-Profile环境切换" class="headerlink" title="81、高级特性-Profile环境切换"></a>81、高级特性-Profile环境切换</h2><p>为了方便多环境适配，Spring Boot简化了profile功能。</p>
<ul>
<li>默认配置文件<code>application.yaml</code>任何时候都会加载。</li>
<li>指定环境配置文件<code>application-&#123;env&#125;.yaml</code>，<code>env</code>通常替代为<code>test</code>，</li>
<li>激活指定环境<ul>
<li>配置文件激活：<code>spring.profiles.active=prod</code></li>
<li>命令行激活：<code>java -jar xxx.jar --spring.profiles.active=prod  --person.name=haha</code>（修改配置文件的任意值，<strong>命令行优先</strong>）</li>
</ul>
</li>
<li>默认配置与环境配置同时生效</li>
<li>同名配置项，profile配置优先</li>
</ul>
<h3 id="Profile条件装配功能"><a href="#Profile条件装配功能" class="headerlink" title="@Profile条件装配功能"></a>@Profile条件装配功能</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@ConfigurationProperties(&quot;person&quot;)</span><span class="comment">//在配置文件中配置</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Person</span>&#123;</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="keyword">private</span> Integer age;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>application.properties</p>
<figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">person:</span> </span><br><span class="line">  <span class="attr">name:</span> <span class="string">lun</span></span><br><span class="line">  <span class="attr">age:</span> <span class="number">8</span></span><br></pre></td></tr></table></figure>

<hr>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">Person</span> &#123;</span><br><span class="line"></span><br><span class="line">   String <span class="title function_">getName</span><span class="params">()</span>;</span><br><span class="line">   Integer <span class="title function_">getAge</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Profile(&quot;test&quot;)</span><span class="comment">//加载application-test.yaml里的</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@ConfigurationProperties(&quot;person&quot;)</span></span><br><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Worker</span> <span class="keyword">implements</span> <span class="title class_">Person</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="keyword">private</span> Integer age;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Profile(value = &#123;&quot;prod&quot;,&quot;default&quot;&#125;)</span><span class="comment">//加载application-prod.yaml里的</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@ConfigurationProperties(&quot;person&quot;)</span></span><br><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Boss</span> <span class="keyword">implements</span> <span class="title class_">Person</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="keyword">private</span> Integer age;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>application-test.yaml</p>
<figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">person:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">test-张三</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">7000</span></span><br></pre></td></tr></table></figure>



<p>application-prod.yaml</p>
<figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">person:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">prod-张三</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8000</span></span><br></pre></td></tr></table></figure>



<p>application.properties</p>
<figure class="highlight properties"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 激活prod配置文件</span></span><br><span class="line"><span class="attr">spring.profiles.active</span>=<span class="string">prod</span></span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> Person person;</span><br><span class="line"></span><br><span class="line"><span class="meta">@GetMapping(&quot;/&quot;)</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">hello</span><span class="params">()</span>&#123;</span><br><span class="line">    <span class="comment">//激活了prod，则返回Boss；激活了test，则返回Worker</span></span><br><span class="line">    <span class="keyword">return</span> person.getClass().toString();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<hr>
<p>@Profile还可以修饰在方法上：</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Color</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyConfig</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Profile(&quot;prod&quot;)</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> Color <span class="title function_">red</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Color</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Profile(&quot;test&quot;)</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> Color <span class="title function_">green</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Color</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<hr>
<p>可以激活一组：</p>
<figure class="highlight properties"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring.profiles.active</span>=<span class="string">production</span></span><br><span class="line"></span><br><span class="line"><span class="attr">spring.profiles.group.production[0]</span>=<span class="string">proddb</span></span><br><span class="line"><span class="attr">spring.profiles.group.production[1]</span>=<span class="string">prodmq</span></span><br></pre></td></tr></table></figure>



<h2 id="82、高级特性-配置加载优先级"><a href="#82、高级特性-配置加载优先级" class="headerlink" title="82、高级特性-配置加载优先级"></a>82、高级特性-配置加载优先级</h2><h3 id="外部化配置"><a href="#外部化配置" class="headerlink" title="外部化配置"></a>外部化配置</h3><p><a target="_blank" rel="noopener" href="https://docs.spring.io/spring-boot/docs/2.4.2/reference/htmlsingle/#boot-features-external-config">官方文档 - Externalized Configuration</a></p>
<p>Spring Boot uses a very particular <code>PropertySource</code> order that is designed to allow sensible overriding of values. Properties are considered in the following order (with values from lower items overriding earlier ones)（1优先级最低，14优先级最高）:</p>
<ol>
<li>Default properties (specified by setting <code>SpringApplication.setDefaultProperties</code>).</li>
<li><a target="_blank" rel="noopener" href="https://docs.spring.io/spring/docs/5.3.3/javadoc-api/org/springframework/context/annotation/PropertySource.html"><code>@PropertySource</code></a> annotations on your <code>@Configuration</code> classes. Please note that such property sources are not added to the <code>Environment</code> until the application context is being refreshed. This is too late to configure certain properties such as <code>logging.*</code> and <code>spring.main.*</code> which are read before refresh begins.</li>
<li>Config data (such as <code>application.properties</code> files)</li>
<li>A <code>RandomValuePropertySource</code> that has properties only in <code>random.*</code>.</li>
<li>OS environment variables.</li>
<li>Java System properties (<code>System.getProperties()</code>).</li>
<li>JNDI attributes from <code>java:comp/env</code>.</li>
<li><code>ServletContext</code> init parameters.</li>
<li><code>ServletConfig</code> init parameters.</li>
<li>Properties from <code>SPRING_APPLICATION_JSON</code> (inline JSON embedded in an environment variable or system property).</li>
<li>Command line arguments.</li>
<li><code>properties</code> attribute on your tests. Available on <a target="_blank" rel="noopener" href="https://docs.spring.io/spring-boot/docs/2.4.2/api/org/springframework/boot/test/context/SpringBootTest.html"><code>@SpringBootTest</code></a> and the <a target="_blank" rel="noopener" href="https://docs.spring.io/spring-boot/docs/2.4.2/reference/htmlsingle/#boot-features-testing-spring-boot-applications-testing-autoconfigured-tests">test annotations for testing a particular slice of your application</a>.</li>
<li><a target="_blank" rel="noopener" href="https://docs.spring.io/spring/docs/5.3.3/javadoc-api/org/springframework/test/context/TestPropertySource.html"><code>@TestPropertySource</code></a> annotations on your tests.</li>
<li><a target="_blank" rel="noopener" href="https://docs.spring.io/spring-boot/docs/2.4.2/reference/htmlsingle/#using-boot-devtools-globalsettings">Devtools global settings properties</a> in the <code>$HOME/.config/spring-boot</code> directory when devtools is active.</li>
</ol>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> org.springframework.stereotype.*;</span><br><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.*;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyBean</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;name&#125;&quot;)</span><span class="comment">//以这种方式可以获得配置值</span></span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<hr>
<ul>
<li>外部配置源<ul>
<li>Java属性文件。</li>
<li>YAML文件。</li>
<li>环境变量。</li>
<li>命令行参数。</li>
</ul>
</li>
<li>配置文件查找位置<ol>
<li>classpath 根路径。</li>
<li>classpath 根路径下config目录。</li>
<li>jar包当前目录。</li>
<li>jar包当前目录的config目录。</li>
<li>&#x2F;config子目录的直接子目录。</li>
</ol>
</li>
<li>配置文件加载顺序：<ol>
<li>当前jar包内部的<code>application.properties</code>和<code>application.yml</code>。</li>
<li>当前jar包内部的<code>application-&#123;profile&#125;.properties</code> 和 <code>application-&#123;profile&#125;.yml</code>。</li>
<li>引用的外部jar包的<code>application.properties</code>和<code>application.yml</code>。</li>
<li>引用的外部jar包的<code>application-&#123;profile&#125;.properties</code>和<code>application-&#123;profile&#125;.yml</code>。</li>
</ol>
</li>
<li>指定环境优先，外部优先，后面的可以覆盖前面的同名配置项。</li>
</ul>
<h2 id="83、高级特性-自定义starter细节"><a href="#83、高级特性-自定义starter细节" class="headerlink" title="83、高级特性-自定义starter细节"></a>83、高级特性-自定义starter细节</h2><h3 id="starter启动原理"><a href="#starter启动原理" class="headerlink" title="starter启动原理"></a>starter启动原理</h3><ul>
<li>starter的pom.xml引入autoconfigure依赖</li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">graph LR</span><br><span class="line">A[starter] --&gt;B[autoconfigure]</span><br><span class="line">B --&gt; C[spring-boot-starter]</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<ul>
<li><p>autoconfigure包中配置使用<code>META-INF/spring.factories</code>中<code>EnableAutoConfiguration</code>的值，使得项目启动加载指定的自动配置类</p>
</li>
<li><p>编写自动配置类 <code>xxxAutoConfiguration</code> -&gt; <code>xxxxProperties</code></p>
</li>
<li><ul>
<li><code>@Configuration</code></li>
<li><code>@Conditional</code></li>
<li><code>@EnableConfigurationProperties</code></li>
<li><code>@Bean</code></li>
<li>……</li>
</ul>
</li>
<li><p>引入starter — <code>xxxAutoConfiguration</code> — 容器中放入组件 —- <code>绑定xxxProperties</code> —- 配置项</p>
</li>
</ul>
<h3 id="自定义starter"><a href="#自定义starter" class="headerlink" title="自定义starter"></a>自定义starter</h3><ul>
<li><p>目标：创建<code>HelloService</code>的自定义starter。</p>
</li>
<li><p>创建两个工程，分别命名为<code>hello-spring-boot-starter</code>（普通Maven工程），<code>hello-spring-boot-starter-autoconfigure</code>（需用用到Spring Initializr创建的Maven工程）。</p>
</li>
<li><p><code>hello-spring-boot-starter</code>无需编写什么代码，只需让该工程引入<code>hello-spring-boot-starter-autoconfigure</code>依赖：</p>
</li>
</ul>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=<span class="string">&quot;1.0&quot;</span> encoding=<span class="string">&quot;UTF-8&quot;</span>?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">&quot;http://maven.apache.org/POM/4.0.0&quot;</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xmlns:xsi</span>=<span class="string">&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xsi:schemaLocation</span>=<span class="string">&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">modelVersion</span>&gt;</span>4.0.0<span class="tag">&lt;/<span class="name">modelVersion</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.lun<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>hello-spring-boot-starter<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.0.0-SNAPSHOT<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.lun<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>hello-spring-boot-starter-autoconfigure<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.0.0-SNAPSHOT<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">project</span>&gt;</span></span><br></pre></td></tr></table></figure>

<ul>
<li><code>hello-spring-boot-starter-autoconfigure</code>的pom.xml如下：</li>
</ul>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=<span class="string">&quot;1.0&quot;</span> encoding=<span class="string">&quot;UTF-8&quot;</span>?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">&quot;http://maven.apache.org/POM/4.0.0&quot;</span> <span class="attr">xmlns:xsi</span>=<span class="string">&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span></span></span><br><span class="line"><span class="tag">	<span class="attr">xsi:schemaLocation</span>=<span class="string">&quot;http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd&quot;</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">modelVersion</span>&gt;</span>4.0.0<span class="tag">&lt;/<span class="name">modelVersion</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">parent</span>&gt;</span></span><br><span class="line">		<span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">		<span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-parent<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">		<span class="tag">&lt;<span class="name">version</span>&gt;</span>2.4.2<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">		<span class="tag">&lt;<span class="name">relativePath</span>/&gt;</span> <span class="comment">&lt;!-- lookup parent from repository --&gt;</span></span><br><span class="line">	<span class="tag">&lt;/<span class="name">parent</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.lun<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>hello-spring-boot-starter-autoconfigure<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">version</span>&gt;</span>1.0.0-SNAPSHOT<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">name</span>&gt;</span>hello-spring-boot-starter-autoconfigure<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">description</span>&gt;</span>Demo project for Spring Boot<span class="tag">&lt;/<span class="name">description</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">properties</span>&gt;</span></span><br><span class="line">		<span class="tag">&lt;<span class="name">java.version</span>&gt;</span>1.8<span class="tag">&lt;/<span class="name">java.version</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;/<span class="name">properties</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">		<span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">			<span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">			<span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">		<span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">project</span>&gt;</span></span><br></pre></td></tr></table></figure>

<ul>
<li>创建4个文件：<ul>
<li><code>com/lun/hello/auto/HelloServiceAutoConfiguration</code></li>
<li><code>com/lun/hello/bean/HelloProperties</code></li>
<li><code>com/lun/hello/service/HelloService</code></li>
<li><code>src/main/resources/META-INF/spring.factories</code></li>
</ul>
</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> com.lun.hello.bean.HelloProperties;</span><br><span class="line"><span class="keyword">import</span> com.lun.hello.service.HelloService;</span><br><span class="line"><span class="keyword">import</span> org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;</span><br><span class="line"><span class="keyword">import</span> org.springframework.boot.context.properties.EnableConfigurationProperties;</span><br><span class="line"><span class="keyword">import</span> org.springframework.context.annotation.Bean;</span><br><span class="line"><span class="keyword">import</span> org.springframework.context.annotation.Configuration;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ConditionalOnMissingBean(HelloService.class)</span></span><br><span class="line"><span class="meta">@EnableConfigurationProperties(HelloProperties.class)</span><span class="comment">//默认HelloProperties放在容器中</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HelloServiceAutoConfiguration</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> HelloService <span class="title function_">helloService</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">HelloService</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> org.springframework.boot.context.properties.ConfigurationProperties;</span><br><span class="line"></span><br><span class="line"><span class="meta">@ConfigurationProperties(&quot;hello&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HelloProperties</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> String prefix;</span><br><span class="line">    <span class="keyword">private</span> String suffix;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getPrefix</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> prefix;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setPrefix</span><span class="params">(String prefix)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.prefix = prefix;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getSuffix</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> suffix;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setSuffix</span><span class="params">(String suffix)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.suffix = suffix;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> com.lun.hello.bean.HelloProperties;</span><br><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.Autowired;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 默认不要放在容器中</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HelloService</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> HelloProperties helloProperties;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">sayHello</span><span class="params">(String userName)</span>&#123;</span><br><span class="line">        <span class="keyword">return</span> helloProperties.getPrefix() + <span class="string">&quot;: &quot;</span> + userName + <span class="string">&quot; &gt; &quot;</span> + helloProperties.getSuffix();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<figure class="highlight properties"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Auto Configure</span></span><br><span class="line"><span class="attr">org.springframework.boot.autoconfigure.EnableAutoConfiguration</span>=<span class="string">\</span></span><br><span class="line"><span class="string">com.lun.hello.auto.HelloServiceAutoConfiguration</span></span><br></pre></td></tr></table></figure>

<ul>
<li><p>用maven插件，将两工程install到本地。</p>
</li>
<li><p>接下来，测试使用自定义starter，用Spring Initializr创建名为<code>hello-spring-boot-starter-test</code>工程，引入<code>hello-spring-boot-starter</code>依赖，其pom.xml如下：</p>
</li>
</ul>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=<span class="string">&quot;1.0&quot;</span> encoding=<span class="string">&quot;UTF-8&quot;</span>?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">&quot;http://maven.apache.org/POM/4.0.0&quot;</span> <span class="attr">xmlns:xsi</span>=<span class="string">&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xsi:schemaLocation</span>=<span class="string">&quot;http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">modelVersion</span>&gt;</span>4.0.0<span class="tag">&lt;/<span class="name">modelVersion</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">parent</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-parent<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.4.2<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">relativePath</span>/&gt;</span> <span class="comment">&lt;!-- lookup parent from repository --&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">parent</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.lun<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>hello-spring-boot-starter-test<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.0.0-SNAPSHOT<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">name</span>&gt;</span>hello-spring-boot-starter-test<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">description</span>&gt;</span>Demo project for Spring Boot<span class="tag">&lt;/<span class="name">description</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">properties</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">java.version</span>&gt;</span>1.8<span class="tag">&lt;/<span class="name">java.version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">properties</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-test<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">scope</span>&gt;</span>test<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!-- 引入`hello-spring-boot-starter`依赖 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.lun<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>hello-spring-boot-starter<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.0.0-SNAPSHOT<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">build</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">plugins</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">plugins</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">build</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">project</span>&gt;</span></span><br><span class="line"></span><br></pre></td></tr></table></figure>

<ul>
<li>添加配置文件<code>application.properties</code>：</li>
</ul>
<figure class="highlight properties"><table><tr><td class="code"><pre><span class="line"><span class="attr">hello.prefix</span>=<span class="string">hello</span></span><br><span class="line"><span class="attr">hello.suffix</span>=<span class="string">666</span></span><br></pre></td></tr></table></figure>

<ul>
<li>添加单元测试类：</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> com.lun.hello.service.HelloService;<span class="comment">//来自自定义starter</span></span><br><span class="line"><span class="keyword">import</span> org.junit.jupiter.api.Assertions;</span><br><span class="line"><span class="keyword">import</span> org.junit.jupiter.api.Test;</span><br><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.Autowired;</span><br><span class="line"><span class="keyword">import</span> org.springframework.boot.test.context.SpringBootTest;</span><br><span class="line"></span><br><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">HelloSpringBootStarterTestApplicationTests</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> HelloService helloService;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">contextLoads</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// System.out.println(helloService.sayHello(&quot;lun&quot;));</span></span><br><span class="line">        Assertions.assertEquals(<span class="string">&quot;hello: lun &gt; 666&quot;</span>, helloService.sayHello(<span class="string">&quot;lun&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h2 id="84、原理解析-SpringApplication创建初始化流程"><a href="#84、原理解析-SpringApplication创建初始化流程" class="headerlink" title="84、原理解析-SpringApplication创建初始化流程"></a>84、原理解析-SpringApplication创建初始化流程</h2><h3 id="SpringBoot启动过程"><a href="#SpringBoot启动过程" class="headerlink" title="SpringBoot启动过程"></a>SpringBoot启动过程</h3><p>Spring Boot应用的启动类：</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> org.springframework.boot.SpringApplication;</span><br><span class="line"><span class="keyword">import</span> org.springframework.boot.autoconfigure.SpringBootApplication;</span><br><span class="line"></span><br><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HelloSpringBootStarterTestApplication</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        SpringApplication.run(HelloSpringBootStarterTestApplication.class, args);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SpringApplication</span> &#123;</span><br><span class="line">    </span><br><span class="line">    ...</span><br><span class="line">    </span><br><span class="line">	<span class="keyword">public</span> <span class="keyword">static</span> ConfigurableApplicationContext <span class="title function_">run</span><span class="params">(Class&lt;?&gt; primarySource, String... args)</span> &#123;</span><br><span class="line">		<span class="keyword">return</span> run(<span class="keyword">new</span> <span class="title class_">Class</span>&lt;?&gt;[] &#123; primarySource &#125;, args);</span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> ConfigurableApplicationContext <span class="title function_">run</span><span class="params">(Class&lt;?&gt;[] primarySources, String[] args)</span> &#123;</span><br><span class="line">		<span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">SpringApplication</span>(primarySources).run(args);</span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//先看看new SpringApplication(primarySources)，下一节再看看run()</span></span><br><span class="line">	<span class="keyword">public</span> <span class="title function_">SpringApplication</span><span class="params">(Class&lt;?&gt;... primarySources)</span> &#123;</span><br><span class="line">		<span class="built_in">this</span>(<span class="literal">null</span>, primarySources);</span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">SpringApplication</span><span class="params">(ResourceLoader resourceLoader, Class&lt;?&gt;... primarySources)</span> &#123;</span><br><span class="line">		<span class="built_in">this</span>.resourceLoader = resourceLoader;</span><br><span class="line">		Assert.notNull(primarySources, <span class="string">&quot;PrimarySources must not be null&quot;</span>);</span><br><span class="line">		<span class="built_in">this</span>.primarySources = <span class="keyword">new</span> <span class="title class_">LinkedHashSet</span>&lt;&gt;(Arrays.asList(primarySources));</span><br><span class="line">        <span class="comment">//WebApplicationType是枚举类，有NONE,SERVLET,REACTIVE,下行webApplicationType是SERVLET</span></span><br><span class="line">		<span class="built_in">this</span>.webApplicationType = WebApplicationType.deduceFromClasspath();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">//初始启动引导器，去spring.factories文件中找org.springframework.boot.Bootstrapper，但我找不到实现Bootstrapper接口的类</span></span><br><span class="line">		<span class="built_in">this</span>.bootstrappers = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(getSpringFactoriesInstances(Bootstrapper.class));</span><br><span class="line">		</span><br><span class="line">        <span class="comment">//去spring.factories找 ApplicationContextInitializer</span></span><br><span class="line">        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));</span><br><span class="line">		</span><br><span class="line">        <span class="comment">//去spring.factories找 ApplicationListener</span></span><br><span class="line">        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));</span><br><span class="line"></span><br><span class="line">        <span class="built_in">this</span>.mainApplicationClass = deduceMainApplicationClass();</span><br><span class="line">	&#125;</span><br><span class="line"> 	</span><br><span class="line">    <span class="keyword">private</span> Class&lt;?&gt; deduceMainApplicationClass() &#123;</span><br><span class="line">		<span class="keyword">try</span> &#123;</span><br><span class="line">			StackTraceElement[] stackTrace = <span class="keyword">new</span> <span class="title class_">RuntimeException</span>().getStackTrace();</span><br><span class="line">			<span class="keyword">for</span> (StackTraceElement stackTraceElement : stackTrace) &#123;</span><br><span class="line">				<span class="keyword">if</span> (<span class="string">&quot;main&quot;</span>.equals(stackTraceElement.getMethodName())) &#123;</span><br><span class="line">					<span class="keyword">return</span> Class.forName(stackTraceElement.getClassName());</span><br><span class="line">				&#125;</span><br><span class="line">			&#125;</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">catch</span> (ClassNotFoundException ex) &#123;</span><br><span class="line">			<span class="comment">// Swallow and continue</span></span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">    ...</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>spring.factories：</p>
<figure class="highlight properties"><table><tr><td class="code"><pre><span class="line"><span class="attr">...</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># Application Context Initializers</span></span><br><span class="line"><span class="attr">org.springframework.context.ApplicationContextInitializer</span>=<span class="string">\</span></span><br><span class="line"><span class="string">org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\</span></span><br><span class="line"><span class="string">org.springframework.boot.context.ContextIdApplicationContextInitializer,\</span></span><br><span class="line"><span class="string">org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\</span></span><br><span class="line"><span class="string">org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\</span></span><br><span class="line"><span class="string">org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># Application Listeners</span></span><br><span class="line"><span class="attr">org.springframework.context.ApplicationListener</span>=<span class="string">\</span></span><br><span class="line"><span class="string">org.springframework.boot.ClearCachesApplicationListener,\</span></span><br><span class="line"><span class="string">org.springframework.boot.builder.ParentContextCloserApplicationListener,\</span></span><br><span class="line"><span class="string">org.springframework.boot.context.FileEncodingApplicationListener,\</span></span><br><span class="line"><span class="string">org.springframework.boot.context.config.AnsiOutputApplicationListener,\</span></span><br><span class="line"><span class="string">org.springframework.boot.context.config.DelegatingApplicationListener,\</span></span><br><span class="line"><span class="string">org.springframework.boot.context.logging.LoggingApplicationListener,\</span></span><br><span class="line"><span class="string">org.springframework.boot.env.EnvironmentPostProcessorApplicationListener,\</span></span><br><span class="line"><span class="string">org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener</span></span><br><span class="line"></span><br><span class="line"><span class="attr">...</span></span><br><span class="line"></span><br></pre></td></tr></table></figure>



<h2 id="85、原理解析-SpringBoot完整启动过程"><a href="#85、原理解析-SpringBoot完整启动过程" class="headerlink" title="85、原理解析-SpringBoot完整启动过程"></a>85、原理解析-SpringBoot完整启动过程</h2><p>继续上一节，接着讨论<code>return new SpringApplication(primarySources).run(args)</code>的<code>run</code>方法</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SpringApplication</span> &#123;</span><br><span class="line">    </span><br><span class="line">    ...</span><br><span class="line">    </span><br><span class="line">	<span class="keyword">public</span> ConfigurableApplicationContext <span class="title function_">run</span><span class="params">(String... args)</span> &#123;</span><br><span class="line">		<span class="type">StopWatch</span> <span class="variable">stopWatch</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StopWatch</span>();<span class="comment">//开始计时器</span></span><br><span class="line">		stopWatch.start();<span class="comment">//开始计时</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">//1.</span></span><br><span class="line">        <span class="comment">//创建引导上下文（Context环境）createBootstrapContext()</span></span><br><span class="line">        <span class="comment">//获取到所有之前的 bootstrappers 挨个执行 intitialize() 来完成对引导启动器上下文环境设置</span></span><br><span class="line">		<span class="type">DefaultBootstrapContext</span> <span class="variable">bootstrapContext</span> <span class="operator">=</span> createBootstrapContext();</span><br><span class="line">		</span><br><span class="line">        <span class="comment">//2.到最后该方法会返回这context</span></span><br><span class="line">        <span class="type">ConfigurableApplicationContext</span> <span class="variable">context</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">		</span><br><span class="line">        <span class="comment">//3.让当前应用进入headless模式</span></span><br><span class="line">        configureHeadlessProperty();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">//4.获取所有 RunListener（运行监听器）,为了方便所有Listener进行事件感知</span></span><br><span class="line">		<span class="type">SpringApplicationRunListeners</span> <span class="variable">listeners</span> <span class="operator">=</span> getRunListeners(args);</span><br><span class="line">		</span><br><span class="line">        <span class="comment">//5. 遍历 SpringApplicationRunListener 调用 starting 方法；</span></span><br><span class="line">		<span class="comment">// 相当于通知所有感兴趣系统正在启动过程的人，项目正在 starting。</span></span><br><span class="line">        listeners.starting(bootstrapContext, <span class="built_in">this</span>.mainApplicationClass);</span><br><span class="line">		<span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">//6.保存命令行参数 ApplicationArguments</span></span><br><span class="line">			<span class="type">ApplicationArguments</span> <span class="variable">applicationArguments</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DefaultApplicationArguments</span>(args);</span><br><span class="line">			</span><br><span class="line">            <span class="comment">//7.准备环境</span></span><br><span class="line">            <span class="type">ConfigurableEnvironment</span> <span class="variable">environment</span> <span class="operator">=</span> prepareEnvironment(listeners, bootstrapContext, applicationArguments);</span><br><span class="line">			configureIgnoreBeanInfo(environment);</span><br><span class="line">			</span><br><span class="line">            <span class="comment">/*打印标志</span></span><br><span class="line"><span class="comment">              .   ____          _            __ _ _</span></span><br><span class="line"><span class="comment">             /\\ / ___&#x27;_ __ _ _(_)_ __  __ _ \ \ \ \</span></span><br><span class="line"><span class="comment">            ( ( )\___ | &#x27;_ | &#x27;_| | &#x27;_ \/ _` | \ \ \ \</span></span><br><span class="line"><span class="comment">             \\/  ___)| |_)| | | | | || (_| |  ) ) ) )</span></span><br><span class="line"><span class="comment">              &#x27;  |____| .__|_| |_|_| |_\__, | / / / /</span></span><br><span class="line"><span class="comment">             =========|_|==============|___/=/_/_/_/</span></span><br><span class="line"><span class="comment">             :: Spring Boot ::                (v2.4.2)</span></span><br><span class="line"><span class="comment">            */</span></span><br><span class="line">            <span class="type">Banner</span> <span class="variable">printedBanner</span> <span class="operator">=</span> printBanner(environment);</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 创建IOC容器（createApplicationContext（））</span></span><br><span class="line">			<span class="comment">// 根据项目类型webApplicationType（NONE,SERVLET,REACTIVE）创建容器，</span></span><br><span class="line">			<span class="comment">// 当前会创建 AnnotationConfigServletWebServerApplicationContext</span></span><br><span class="line">			context = createApplicationContext();</span><br><span class="line">			context.setApplicationStartup(<span class="built_in">this</span>.applicationStartup);</span><br><span class="line">            </span><br><span class="line">            <span class="comment">//8.准备ApplicationContext IOC容器的基本信息</span></span><br><span class="line">			prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);</span><br><span class="line">			<span class="comment">//9.刷新IOC容器,创建容器中的所有组件,Spring框架的内容</span></span><br><span class="line">            refreshContext(context);</span><br><span class="line">			<span class="comment">//该方法没内容，大概为将来填入</span></span><br><span class="line">			afterRefresh(context, applicationArguments);</span><br><span class="line">			stopWatch.stop();<span class="comment">//停止计时</span></span><br><span class="line">			<span class="keyword">if</span> (<span class="built_in">this</span>.logStartupInfo) &#123;<span class="comment">//this.logStartupInfo默认是true</span></span><br><span class="line">				<span class="keyword">new</span> <span class="title class_">StartupInfoLogger</span>(<span class="built_in">this</span>.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);</span><br><span class="line">			&#125;</span><br><span class="line">            <span class="comment">//10.</span></span><br><span class="line">			listeners.started(context);</span><br><span class="line">            </span><br><span class="line">            <span class="comment">//11.调用所有runners</span></span><br><span class="line">			callRunners(context, applicationArguments);</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">catch</span> (Throwable ex) &#123;</span><br><span class="line">            <span class="comment">//13.</span></span><br><span class="line">			handleRunFailure(context, ex, listeners);</span><br><span class="line">			<span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(ex);</span><br><span class="line">		&#125;</span><br><span class="line"></span><br><span class="line">		<span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">//12.</span></span><br><span class="line">			listeners.running(context);</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">catch</span> (Throwable ex) &#123;</span><br><span class="line">            <span class="comment">//13.</span></span><br><span class="line">			handleRunFailure(context, ex, <span class="literal">null</span>);</span><br><span class="line">			<span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(ex);</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">return</span> context;</span><br><span class="line">	&#125;</span><br><span class="line"> </span><br><span class="line">    <span class="comment">//1. </span></span><br><span class="line">    <span class="keyword">private</span> DefaultBootstrapContext <span class="title function_">createBootstrapContext</span><span class="params">()</span> &#123;</span><br><span class="line">		<span class="type">DefaultBootstrapContext</span> <span class="variable">bootstrapContext</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DefaultBootstrapContext</span>();</span><br><span class="line">		<span class="built_in">this</span>.bootstrappers.forEach((initializer) -&gt; initializer.intitialize(bootstrapContext));</span><br><span class="line">		<span class="keyword">return</span> bootstrapContext;</span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//3.</span></span><br><span class="line">   	<span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">configureHeadlessProperty</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">//this.headless默认为true</span></span><br><span class="line">		System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,</span><br><span class="line">				System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(<span class="built_in">this</span>.headless)));</span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">SYSTEM_PROPERTY_JAVA_AWT_HEADLESS</span> <span class="operator">=</span> <span class="string">&quot;java.awt.headless&quot;</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//4.</span></span><br><span class="line">    <span class="keyword">private</span> SpringApplicationRunListeners <span class="title function_">getRunListeners</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">		Class&lt;?&gt;[] types = <span class="keyword">new</span> <span class="title class_">Class</span>&lt;?&gt;[] &#123; SpringApplication.class, String[].class &#125;;</span><br><span class="line">		<span class="comment">//getSpringFactoriesInstances 去 spring.factories 找 SpringApplicationRunListener</span></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">SpringApplicationRunListeners</span>(logger,</span><br><span class="line">				getSpringFactoriesInstances(SpringApplicationRunListener.class, types, <span class="built_in">this</span>, args),</span><br><span class="line">				<span class="built_in">this</span>.applicationStartup);</span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//7.准备环境</span></span><br><span class="line">    <span class="keyword">private</span> ConfigurableEnvironment <span class="title function_">prepareEnvironment</span><span class="params">(SpringApplicationRunListeners listeners,</span></span><br><span class="line"><span class="params">			DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments)</span> &#123;</span><br><span class="line">		<span class="comment">// Create and configure the environment</span></span><br><span class="line">        <span class="comment">//返回或者创建基础环境信息对象，如：StandardServletEnvironment, StandardReactiveWebEnvironment</span></span><br><span class="line">		<span class="type">ConfigurableEnvironment</span> <span class="variable">environment</span> <span class="operator">=</span> getOrCreateEnvironment();</span><br><span class="line">        <span class="comment">//配置环境信息对象,读取所有的配置源的配置属性值。</span></span><br><span class="line">		configureEnvironment(environment, applicationArguments.getSourceArgs());</span><br><span class="line">		<span class="comment">//绑定环境信息</span></span><br><span class="line">        ConfigurationPropertySources.attach(environment);</span><br><span class="line">        <span class="comment">//7.1 通知所有的监听器当前环境准备完成</span></span><br><span class="line">		listeners.environmentPrepared(bootstrapContext, environment);</span><br><span class="line">		DefaultPropertiesPropertySource.moveToEnd(environment);</span><br><span class="line">		configureAdditionalProfiles(environment);</span><br><span class="line">		bindToSpringApplication(environment);</span><br><span class="line">		<span class="keyword">if</span> (!<span class="built_in">this</span>.isCustomEnvironment) &#123;</span><br><span class="line">			environment = <span class="keyword">new</span> <span class="title class_">EnvironmentConverter</span>(getClassLoader()).convertEnvironmentIfNecessary(environment,</span><br><span class="line">					deduceEnvironmentClass());</span><br><span class="line">		&#125;</span><br><span class="line">		ConfigurationPropertySources.attach(environment);</span><br><span class="line">		<span class="keyword">return</span> environment;</span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//8.</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">prepareContext</span><span class="params">(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,</span></span><br><span class="line"><span class="params">			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,</span></span><br><span class="line"><span class="params">			ApplicationArguments applicationArguments, Banner printedBanner)</span> &#123;</span><br><span class="line">		<span class="comment">//保存环境信息</span></span><br><span class="line">        context.setEnvironment(environment);</span><br><span class="line">        <span class="comment">//IOC容器的后置处理流程</span></span><br><span class="line">		postProcessApplicationContext(context);</span><br><span class="line">        <span class="comment">//应用初始化器</span></span><br><span class="line">		applyInitializers(context);</span><br><span class="line">        <span class="comment">//8.1 遍历所有的 listener 调用 contextPrepared。</span></span><br><span class="line">        <span class="comment">//EventPublishRunListenr通知所有的监听器contextPrepared</span></span><br><span class="line">		listeners.contextPrepared(context);</span><br><span class="line">		bootstrapContext.close(context);</span><br><span class="line">		<span class="keyword">if</span> (<span class="built_in">this</span>.logStartupInfo) &#123;</span><br><span class="line">			logStartupInfo(context.getParent() == <span class="literal">null</span>);</span><br><span class="line">			logStartupProfileInfo(context);</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="comment">// Add boot specific singleton beans</span></span><br><span class="line">		<span class="type">ConfigurableListableBeanFactory</span> <span class="variable">beanFactory</span> <span class="operator">=</span> context.getBeanFactory();</span><br><span class="line">		beanFactory.registerSingleton(<span class="string">&quot;springApplicationArguments&quot;</span>, applicationArguments);</span><br><span class="line">		<span class="keyword">if</span> (printedBanner != <span class="literal">null</span>) &#123;</span><br><span class="line">			beanFactory.registerSingleton(<span class="string">&quot;springBootBanner&quot;</span>, printedBanner);</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">if</span> (beanFactory <span class="keyword">instanceof</span> DefaultListableBeanFactory) &#123;</span><br><span class="line">			((DefaultListableBeanFactory) beanFactory)</span><br><span class="line">					.setAllowBeanDefinitionOverriding(<span class="built_in">this</span>.allowBeanDefinitionOverriding);</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">if</span> (<span class="built_in">this</span>.lazyInitialization) &#123;</span><br><span class="line">			context.addBeanFactoryPostProcessor(<span class="keyword">new</span> <span class="title class_">LazyInitializationBeanFactoryPostProcessor</span>());</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="comment">// Load the sources</span></span><br><span class="line">		Set&lt;Object&gt; sources = getAllSources();</span><br><span class="line">		Assert.notEmpty(sources, <span class="string">&quot;Sources must not be empty&quot;</span>);</span><br><span class="line">		load(context, sources.toArray(<span class="keyword">new</span> <span class="title class_">Object</span>[<span class="number">0</span>]));</span><br><span class="line">        <span class="comment">//8.2</span></span><br><span class="line">		listeners.contextLoaded(context);</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//11.调用所有runners</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">callRunners</span><span class="params">(ApplicationContext context, ApplicationArguments args)</span> &#123;</span><br><span class="line">		List&lt;Object&gt; runners = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">//获取容器中的 ApplicationRunner</span></span><br><span class="line">		runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());</span><br><span class="line">		<span class="comment">//获取容器中的  CommandLineRunner</span></span><br><span class="line">        runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());</span><br><span class="line">        <span class="comment">//合并所有runner并且按照@Order进行排序</span></span><br><span class="line">		AnnotationAwareOrderComparator.sort(runners);</span><br><span class="line">        <span class="comment">//遍历所有的runner。调用 run 方法</span></span><br><span class="line">		<span class="keyword">for</span> (Object runner : <span class="keyword">new</span> <span class="title class_">LinkedHashSet</span>&lt;&gt;(runners)) &#123;</span><br><span class="line">			<span class="keyword">if</span> (runner <span class="keyword">instanceof</span> ApplicationRunner) &#123;</span><br><span class="line">				callRunner((ApplicationRunner) runner, args);</span><br><span class="line">			&#125;</span><br><span class="line">			<span class="keyword">if</span> (runner <span class="keyword">instanceof</span> CommandLineRunner) &#123;</span><br><span class="line">				callRunner((CommandLineRunner) runner, args);</span><br><span class="line">			&#125;</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//13.</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">handleRunFailure</span><span class="params">(ConfigurableApplicationContext context, Throwable exception,</span></span><br><span class="line"><span class="params">			SpringApplicationRunListeners listeners)</span> &#123;</span><br><span class="line">		<span class="keyword">try</span> &#123;</span><br><span class="line">			<span class="keyword">try</span> &#123;</span><br><span class="line">				handleExitCode(context, exception);</span><br><span class="line">				<span class="keyword">if</span> (listeners != <span class="literal">null</span>) &#123;</span><br><span class="line">                    <span class="comment">//14.</span></span><br><span class="line">					listeners.failed(context, exception);</span><br><span class="line">				&#125;</span><br><span class="line">			&#125;</span><br><span class="line">			<span class="keyword">finally</span> &#123;</span><br><span class="line">				reportFailure(getExceptionReporters(context), exception);</span><br><span class="line">				<span class="keyword">if</span> (context != <span class="literal">null</span>) &#123;</span><br><span class="line">					context.close();</span><br><span class="line">				&#125;</span><br><span class="line">			&#125;</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">catch</span> (Exception ex) &#123;</span><br><span class="line">			logger.warn(<span class="string">&quot;Unable to close ApplicationContext&quot;</span>, ex);</span><br><span class="line">		&#125;</span><br><span class="line">		ReflectionUtils.rethrowRuntimeException(exception);</span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//2. new SpringApplication(primarySources).run(args) 最后返回的接口类型</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">ConfigurableApplicationContext</span> <span class="keyword">extends</span> <span class="title class_">ApplicationContext</span>, Lifecycle, Closeable &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">CONFIG_LOCATION_DELIMITERS</span> <span class="operator">=</span> <span class="string">&quot;,; \t\n&quot;</span>;</span><br><span class="line">    <span class="type">String</span> <span class="variable">CONVERSION_SERVICE_BEAN_NAME</span> <span class="operator">=</span> <span class="string">&quot;conversionService&quot;</span>;</span><br><span class="line">    <span class="type">String</span> <span class="variable">LOAD_TIME_WEAVER_BEAN_NAME</span> <span class="operator">=</span> <span class="string">&quot;loadTimeWeaver&quot;</span>;</span><br><span class="line">    <span class="type">String</span> <span class="variable">ENVIRONMENT_BEAN_NAME</span> <span class="operator">=</span> <span class="string">&quot;environment&quot;</span>;</span><br><span class="line">    <span class="type">String</span> <span class="variable">SYSTEM_PROPERTIES_BEAN_NAME</span> <span class="operator">=</span> <span class="string">&quot;systemProperties&quot;</span>;</span><br><span class="line">    <span class="type">String</span> <span class="variable">SYSTEM_ENVIRONMENT_BEAN_NAME</span> <span class="operator">=</span> <span class="string">&quot;systemEnvironment&quot;</span>;</span><br><span class="line">    <span class="type">String</span> <span class="variable">APPLICATION_STARTUP_BEAN_NAME</span> <span class="operator">=</span> <span class="string">&quot;applicationStartup&quot;</span>;</span><br><span class="line">    <span class="type">String</span> <span class="variable">SHUTDOWN_HOOK_THREAD_NAME</span> <span class="operator">=</span> <span class="string">&quot;SpringContextShutdownHook&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">setId</span><span class="params">(String var1)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">setParent</span><span class="params">(<span class="meta">@Nullable</span> ApplicationContext var1)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">setEnvironment</span><span class="params">(ConfigurableEnvironment var1)</span>;</span><br><span class="line"></span><br><span class="line">    ConfigurableEnvironment <span class="title function_">getEnvironment</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">setApplicationStartup</span><span class="params">(ApplicationStartup var1)</span>;</span><br><span class="line"></span><br><span class="line">    ApplicationStartup <span class="title function_">getApplicationStartup</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">addBeanFactoryPostProcessor</span><span class="params">(BeanFactoryPostProcessor var1)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">addApplicationListener</span><span class="params">(ApplicationListener&lt;?&gt; var1)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">setClassLoader</span><span class="params">(ClassLoader var1)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">addProtocolResolver</span><span class="params">(ProtocolResolver var1)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">refresh</span><span class="params">()</span> <span class="keyword">throws</span> BeansException, IllegalStateException;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">registerShutdownHook</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">close</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line">    <span class="type">boolean</span> <span class="title function_">isActive</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line">    ConfigurableListableBeanFactory <span class="title function_">getBeanFactory</span><span class="params">()</span> <span class="keyword">throws</span> IllegalStateException;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<figure class="highlight properties"><table><tr><td class="code"><pre><span class="line"><span class="comment">#4.</span></span><br><span class="line"><span class="comment">#spring.factories</span></span><br><span class="line"><span class="comment"># Run Listeners</span></span><br><span class="line"><span class="attr">org.springframework.boot.SpringApplicationRunListener</span>=<span class="string">\</span></span><br><span class="line"><span class="string">org.springframework.boot.context.event.EventPublishingRunListener</span></span><br></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">SpringApplicationRunListeners</span> &#123;</span><br><span class="line"></span><br><span class="line">	<span class="keyword">private</span> <span class="keyword">final</span> Log log;</span><br><span class="line"></span><br><span class="line">	<span class="keyword">private</span> <span class="keyword">final</span> List&lt;SpringApplicationRunListener&gt; listeners;</span><br><span class="line"></span><br><span class="line">	<span class="keyword">private</span> <span class="keyword">final</span> ApplicationStartup applicationStartup;</span><br><span class="line"></span><br><span class="line">	SpringApplicationRunListeners(Log log, Collection&lt;? <span class="keyword">extends</span> <span class="title class_">SpringApplicationRunListener</span>&gt; listeners,</span><br><span class="line">			ApplicationStartup applicationStartup) &#123;</span><br><span class="line">		<span class="built_in">this</span>.log = log;</span><br><span class="line">		<span class="built_in">this</span>.listeners = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(listeners);</span><br><span class="line">		<span class="built_in">this</span>.applicationStartup = applicationStartup;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//5.遍历 SpringApplicationRunListener 调用 starting 方法；</span></span><br><span class="line">	<span class="comment">//相当于通知所有感兴趣系统正在启动过程的人，项目正在 starting。</span></span><br><span class="line">	<span class="keyword">void</span> <span class="title function_">starting</span><span class="params">(ConfigurableBootstrapContext bootstrapContext, Class&lt;?&gt; mainApplicationClass)</span> &#123;</span><br><span class="line">		doWithListeners(<span class="string">&quot;spring.boot.application.starting&quot;</span>, (listener) -&gt; listener.starting(bootstrapContext),</span><br><span class="line">				(step) -&gt; &#123;</span><br><span class="line">					<span class="keyword">if</span> (mainApplicationClass != <span class="literal">null</span>) &#123;</span><br><span class="line">						step.tag(<span class="string">&quot;mainApplicationClass&quot;</span>, mainApplicationClass.getName());</span><br><span class="line">					&#125;</span><br><span class="line">				&#125;);</span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//7.1</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">environmentPrepared</span><span class="params">(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment)</span> &#123;</span><br><span class="line">		doWithListeners(<span class="string">&quot;spring.boot.application.environment-prepared&quot;</span>,</span><br><span class="line">				(listener) -&gt; listener.environmentPrepared(bootstrapContext, environment));</span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//8.1</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">contextPrepared</span><span class="params">(ConfigurableApplicationContext context)</span> &#123;</span><br><span class="line">		doWithListeners(<span class="string">&quot;spring.boot.application.context-prepared&quot;</span>, (listener) -&gt; listener.contextPrepared(context));</span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//8.2</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">contextLoaded</span><span class="params">(ConfigurableApplicationContext context)</span> &#123;</span><br><span class="line">		doWithListeners(<span class="string">&quot;spring.boot.application.context-loaded&quot;</span>, (listener) -&gt; listener.contextLoaded(context));</span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//10.</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">started</span><span class="params">(ConfigurableApplicationContext context)</span> &#123;</span><br><span class="line">		doWithListeners(<span class="string">&quot;spring.boot.application.started&quot;</span>, (listener) -&gt; listener.started(context));</span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//12.</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">running</span><span class="params">(ConfigurableApplicationContext context)</span> &#123;</span><br><span class="line">		doWithListeners(<span class="string">&quot;spring.boot.application.running&quot;</span>, (listener) -&gt; listener.running(context));</span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//14.</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">failed</span><span class="params">(ConfigurableApplicationContext context, Throwable exception)</span> &#123;</span><br><span class="line">		doWithListeners(<span class="string">&quot;spring.boot.application.failed&quot;</span>,</span><br><span class="line">				(listener) -&gt; callFailedListener(listener, context, exception), (step) -&gt; &#123;</span><br><span class="line">					step.tag(<span class="string">&quot;exception&quot;</span>, exception.getClass().toString());</span><br><span class="line">					step.tag(<span class="string">&quot;message&quot;</span>, exception.getMessage());</span><br><span class="line">				&#125;);</span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">doWithListeners</span><span class="params">(String stepName, Consumer&lt;SpringApplicationRunListener&gt; listenerAction,</span></span><br><span class="line"><span class="params">			Consumer&lt;StartupStep&gt; stepAction)</span> &#123;</span><br><span class="line">		<span class="type">StartupStep</span> <span class="variable">step</span> <span class="operator">=</span> <span class="built_in">this</span>.applicationStartup.start(stepName);</span><br><span class="line">		<span class="built_in">this</span>.listeners.forEach(listenerAction);</span><br><span class="line">		<span class="keyword">if</span> (stepAction != <span class="literal">null</span>) &#123;</span><br><span class="line">			stepAction.accept(step);</span><br><span class="line">		&#125;</span><br><span class="line">		step.end();</span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">    ...</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h2 id="86、原理解析-自定义事件监听组件"><a href="#86、原理解析-自定义事件监听组件" class="headerlink" title="86、原理解析-自定义事件监听组件"></a>86、原理解析-自定义事件监听组件</h2><p><code>MyApplicationContextInitializer.java</code></p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> org.springframework.context.ApplicationContextInitializer;</span><br><span class="line"><span class="keyword">import</span> org.springframework.context.ConfigurableApplicationContext;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyApplicationContextInitializer</span> <span class="keyword">implements</span> <span class="title class_">ApplicationContextInitializer</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">initialize</span><span class="params">(ConfigurableApplicationContext applicationContext)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;MyApplicationContextInitializer ....initialize.... &quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><code>MyApplicationListener.java</code></p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> org.springframework.context.ApplicationEvent;</span><br><span class="line"><span class="keyword">import</span> org.springframework.context.ApplicationListener;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyApplicationListener</span> <span class="keyword">implements</span> <span class="title class_">ApplicationListener</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onApplicationEvent</span><span class="params">(ApplicationEvent event)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;MyApplicationListener.....onApplicationEvent...&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><code>MyApplicationRunner.java</code></p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> org.springframework.boot.ApplicationArguments;</span><br><span class="line"><span class="keyword">import</span> org.springframework.boot.ApplicationRunner;</span><br><span class="line"><span class="keyword">import</span> org.springframework.core.annotation.Order;</span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Component;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@Order(1)</span></span><br><span class="line"><span class="meta">@Component</span><span class="comment">//放入容器</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyApplicationRunner</span> <span class="keyword">implements</span> <span class="title class_">ApplicationRunner</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">(ApplicationArguments args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;MyApplicationRunner...run...&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><code>MyCommandLineRunner.java</code></p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> org.springframework.boot.CommandLineRunner;</span><br><span class="line"><span class="keyword">import</span> org.springframework.core.annotation.Order;</span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Component;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 应用启动做一个一次性事情</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Order(2)</span></span><br><span class="line"><span class="meta">@Component</span><span class="comment">//放入容器</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyCommandLineRunner</span> <span class="keyword">implements</span> <span class="title class_">CommandLineRunner</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">(String... args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;MyCommandLineRunner....run....&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><code>MySpringApplicationRunListener.java</code></p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> org.springframework.boot.ConfigurableBootstrapContext;</span><br><span class="line"><span class="keyword">import</span> org.springframework.boot.SpringApplication;</span><br><span class="line"><span class="keyword">import</span> org.springframework.boot.SpringApplicationRunListener;</span><br><span class="line"><span class="keyword">import</span> org.springframework.context.ConfigurableApplicationContext;</span><br><span class="line"><span class="keyword">import</span> org.springframework.core.env.ConfigurableEnvironment;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MySpringApplicationRunListener</span> <span class="keyword">implements</span> <span class="title class_">SpringApplicationRunListener</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> SpringApplication application;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">MySpringApplicationRunListener</span><span class="params">(SpringApplication application, String[] args)</span>&#123;</span><br><span class="line">        <span class="built_in">this</span>.application = application;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">starting</span><span class="params">(ConfigurableBootstrapContext bootstrapContext)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;MySpringApplicationRunListener....starting....&quot;</span>);</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">environmentPrepared</span><span class="params">(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;MySpringApplicationRunListener....environmentPrepared....&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">contextPrepared</span><span class="params">(ConfigurableApplicationContext context)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;MySpringApplicationRunListener....contextPrepared....&quot;</span>);</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">contextLoaded</span><span class="params">(ConfigurableApplicationContext context)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;MySpringApplicationRunListener....contextLoaded....&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">started</span><span class="params">(ConfigurableApplicationContext context)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;MySpringApplicationRunListener....started....&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">running</span><span class="params">(ConfigurableApplicationContext context)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;MySpringApplicationRunListener....running....&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">failed</span><span class="params">(ConfigurableApplicationContext context, Throwable exception)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;MySpringApplicationRunListener....failed....&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>注册<code>MyApplicationContextInitializer</code>，<code>MyApplicationListener</code>，<code>MySpringApplicationRunListener</code>:</p>
<p><code>resources / META-INF / spring.factories</code>:</p>
<figure class="highlight properties"><table><tr><td class="code"><pre><span class="line"><span class="attr">org.springframework.context.ApplicationContextInitializer</span>=<span class="string">\</span></span><br><span class="line"><span class="string">  com.lun.boot.listener.MyApplicationContextInitializer</span></span><br><span class="line"></span><br><span class="line"><span class="attr">org.springframework.context.ApplicationListener</span>=<span class="string">\</span></span><br><span class="line"><span class="string">  com.lun.boot.listener.MyApplicationListener</span></span><br><span class="line"></span><br><span class="line"><span class="attr">org.springframework.boot.SpringApplicationRunListener</span>=<span class="string">\</span></span><br><span class="line"><span class="string">  com.lun.boot.listener.MySpringApplicationRunListener</span></span><br></pre></td></tr></table></figure>



<h2 id="87、后会有期"><a href="#87、后会有期" class="headerlink" title="87、后会有期"></a>87、后会有期</h2><blockquote>
<p>路漫漫其修远兮，吾将上下而求索。</p>
</blockquote>
<blockquote>
<p>纸上得来终觉浅，绝知此事要躬行。</p>
</blockquote>
<ul>
<li><p>Spring Boot 2 场景整合篇</p>
<ul>
<li>虚拟化技术</li>
<li>安全控制</li>
<li>缓存技术</li>
<li>消息中间件</li>
<li>对象存储</li>
<li>定时调度</li>
<li>异步任务</li>
<li>分布式系统</li>
</ul>
</li>
<li><p>Spring Boot 2 响应式编程</p>
<ul>
<li>响应式编程基础</li>
<li>Webflux开发Web应用</li>
<li>响应式访问持久化层</li>
<li>响应式安全开发</li>
<li>响应式原理</li>
</ul>
</li>
</ul>
</article><div class="post-copyright"><div class="post-copyright__author"><span class="post-copyright-meta">文章作者: </span><span class="post-copyright-info"><a href="https://gitee.com/zcmmmm/zcmmmm">zcm</a></span></div><div class="post-copyright__type"><span class="post-copyright-meta">文章链接: </span><span class="post-copyright-info"><a href="https://gitee.com/zcmmmm/zcmmmm/2022/09/23/SpringBoot2%E5%85%A5%E9%97%A8-02/">https://gitee.com/zcmmmm/zcmmmm/2022/09/23/SpringBoot2%E5%85%A5%E9%97%A8-02/</a></span></div><div class="post-copyright__notice"><span class="post-copyright-meta">版权声明: </span><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来自 <a href="https://gitee.com/zcmmmm/zcmmmm" target="_blank">无人深空</a>！</span></div></div><div class="tag_share"><div class="post-meta__tag-list"><a class="post-meta__tags" href="/tags/SpringBoot2/">SpringBoot2</a></div><div class="post_share"><div class="social-share" data-image="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Spring/Spring.jpg" data-sites="facebook,twitter,wechat,weibo,qq"></div><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/butterfly-extsrc/sharejs/dist/css/share.min.css" media="print" onload="this.media='all'"><script src="https://cdn.jsdelivr.net/npm/butterfly-extsrc/sharejs/dist/js/social-share.min.js" defer></script></div></div><nav class="pagination-post" id="pagination"><div class="prev-post pull-left"><a href="/2022/09/24/JUC%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/"><img class="prev-cover" src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JUC/JUC.jpg" onerror="onerror=null;src='/img/404.jpg'" alt="cover of previous post"><div class="pagination-info"><div class="label">上一篇</div><div class="prev_info">JUC并发编程</div></div></a></div><div class="next-post pull-right"><a href="/2022/09/23/SpringBoot2%E5%85%A5%E9%97%A8-01/"><img class="next-cover" src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Spring/Spring.jpg" onerror="onerror=null;src='/img/404.jpg'" alt="cover of next post"><div class="pagination-info"><div class="label">下一篇</div><div class="next_info">SpringBoot2入门-01</div></div></a></div></nav><div class="relatedPosts"><div class="headline"><i class="fas fa-thumbs-up fa-fw"></i><span>相关推荐</span></div><div class="relatedPosts-list"><div><a href="/2022/09/23/SpringBoot2%E5%85%A5%E9%97%A8-01/" title="SpringBoot2入门-01"><img class="cover" src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Spring/Spring.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2022-09-23</div><div class="title">SpringBoot2入门-01</div></div></a></div></div></div></div><div class="aside-content" id="aside-content"><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="fas fa-stream"></i><span>目录</span><span class="toc-percentage"></span></div><div class="toc-content is-expand"><ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#45%E3%80%81web%E5%AE%9E%E9%AA%8C-%E6%8A%BD%E5%8F%96%E5%85%AC%E5%85%B1%E9%A1%B5%E9%9D%A2"><span class="toc-text">45、web实验-抽取公共页面</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#46%E3%80%81web%E5%AE%9E%E9%AA%8C-%E9%81%8D%E5%8E%86%E6%95%B0%E6%8D%AE%E4%B8%8E%E9%A1%B5%E9%9D%A2bug%E4%BF%AE%E6%94%B9"><span class="toc-text">46、web实验-遍历数据与页面bug修改</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#47%E3%80%81%E8%A7%86%E5%9B%BE%E8%A7%A3%E6%9E%90-%E3%80%90%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E3%80%91-%E8%A7%86%E5%9B%BE%E8%A7%A3%E6%9E%90%E5%99%A8%E4%B8%8E%E8%A7%86%E5%9B%BE"><span class="toc-text">47、视图解析-【源码分析】-视图解析器与视图</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#48%E3%80%81%E6%8B%A6%E6%88%AA%E5%99%A8-%E7%99%BB%E5%BD%95%E6%A3%80%E6%9F%A5%E4%B8%8E%E9%9D%99%E6%80%81%E8%B5%84%E6%BA%90%E6%94%BE%E8%A1%8C"><span class="toc-text">48、拦截器-登录检查与静态资源放行</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#49%E3%80%81%E6%8B%A6%E6%88%AA%E5%99%A8-%E3%80%90%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E3%80%91-%E6%8B%A6%E6%88%AA%E5%99%A8%E7%9A%84%E6%89%A7%E8%A1%8C%E6%97%B6%E6%9C%BA%E5%92%8C%E5%8E%9F%E7%90%86"><span class="toc-text">49、拦截器-【源码分析】-拦截器的执行时机和原理</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#50%E3%80%81%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0-%E5%8D%95%E6%96%87%E4%BB%B6%E4%B8%8E%E5%A4%9A%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0%E7%9A%84%E4%BD%BF%E7%94%A8"><span class="toc-text">50、文件上传-单文件与多文件上传的使用</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#51%E3%80%81%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0-%E3%80%90%E6%BA%90%E7%A0%81%E6%B5%81%E7%A8%8B%E3%80%91%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0%E5%8F%82%E6%95%B0%E8%A7%A3%E6%9E%90%E5%99%A8"><span class="toc-text">51、文件上传-【源码流程】文件上传参数解析器</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#52%E3%80%81%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86-SpringBoot%E9%BB%98%E8%AE%A4%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86%E6%9C%BA%E5%88%B6"><span class="toc-text">52、错误处理-SpringBoot默认错误处理机制</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#53%E3%80%81%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86-%E3%80%90%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E3%80%91%E5%BA%95%E5%B1%82%E7%BB%84%E4%BB%B6%E5%8A%9F%E8%83%BD%E5%88%86%E6%9E%90"><span class="toc-text">53、错误处理-【源码分析】底层组件功能分析</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#54%E3%80%81%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86-%E3%80%90%E6%BA%90%E7%A0%81%E6%B5%81%E7%A8%8B%E3%80%91%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86%E6%B5%81%E7%A8%8B"><span class="toc-text">54、错误处理-【源码流程】异常处理流程</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#55%E3%80%81%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86-%E3%80%90%E6%BA%90%E7%A0%81%E6%B5%81%E7%A8%8B%E3%80%91%E5%87%A0%E7%A7%8D%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86%E5%8E%9F%E7%90%86"><span class="toc-text">55、错误处理-【源码流程】几种异常处理原理</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#56%E3%80%81%E5%8E%9F%E7%94%9F%E7%BB%84%E4%BB%B6%E6%B3%A8%E5%85%A5-%E5%8E%9F%E7%94%9F%E6%B3%A8%E8%A7%A3%E4%B8%8ESpring%E6%96%B9%E5%BC%8F%E6%B3%A8%E5%85%A5"><span class="toc-text">56、原生组件注入-原生注解与Spring方式注入</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8%E5%8E%9F%E7%94%9F%E7%9A%84%E6%B3%A8%E8%A7%A3"><span class="toc-text">使用原生的注解</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Spring%E6%96%B9%E5%BC%8F%E6%B3%A8%E5%85%A5"><span class="toc-text">Spring方式注入</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#57%E3%80%81%E5%8E%9F%E7%94%9F%E7%BB%84%E4%BB%B6%E6%B3%A8%E5%85%A5-%E3%80%90%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E3%80%91DispatcherServlet%E6%B3%A8%E5%85%A5%E5%8E%9F%E7%90%86"><span class="toc-text">57、原生组件注入-【源码分析】DispatcherServlet注入原理</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#58%E3%80%81%E5%B5%8C%E5%85%A5%E5%BC%8FServlet%E5%AE%B9%E5%99%A8-%E3%80%90%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E3%80%91%E5%88%87%E6%8D%A2web%E6%9C%8D%E5%8A%A1%E5%99%A8%E4%B8%8E%E5%AE%9A%E5%88%B6%E5%8C%96"><span class="toc-text">58、嵌入式Servlet容器-【源码分析】切换web服务器与定制化</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%AE%9A%E5%88%B6Servlet%E5%AE%B9%E5%99%A8"><span class="toc-text">定制Servlet容器</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#59%E3%80%81%E5%AE%9A%E5%88%B6%E5%8C%96%E5%8E%9F%E7%90%86-SpringBoot%E5%AE%9A%E5%88%B6%E5%8C%96%E7%BB%84%E4%BB%B6%E7%9A%84%E5%87%A0%E7%A7%8D%E6%96%B9%E5%BC%8F%EF%BC%88%E5%B0%8F%E7%BB%93%EF%BC%89"><span class="toc-text">59、定制化原理-SpringBoot定制化组件的几种方式（小结）</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%AE%9A%E5%88%B6%E5%8C%96%E7%9A%84%E5%B8%B8%E8%A7%81%E6%96%B9%E5%BC%8F"><span class="toc-text">定制化的常见方式</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90%E5%A5%97%E8%B7%AF"><span class="toc-text">原理分析套路</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#60%E3%80%81%E6%95%B0%E6%8D%AE%E8%AE%BF%E9%97%AE-%E6%95%B0%E6%8D%AE%E5%BA%93%E5%9C%BA%E6%99%AF%E7%9A%84%E8%87%AA%E5%8A%A8%E9%85%8D%E7%BD%AE%E5%88%86%E6%9E%90%E4%B8%8E%E6%95%B4%E5%90%88%E6%B5%8B%E8%AF%95"><span class="toc-text">60、数据访问-数据库场景的自动配置分析与整合测试</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%AF%BC%E5%85%A5JDBC%E5%9C%BA%E6%99%AF"><span class="toc-text">导入JDBC场景</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E7%9B%B8%E5%85%B3%E6%95%B0%E6%8D%AE%E6%BA%90%E9%85%8D%E7%BD%AE%E7%B1%BB"><span class="toc-text">相关数据源配置类</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BF%AE%E6%94%B9%E9%85%8D%E7%BD%AE%E9%A1%B9"><span class="toc-text">修改配置项</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E6%95%B0%E6%8D%AE%E6%BA%90"><span class="toc-text">单元测试数据源</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#61%E3%80%81%E6%95%B0%E6%8D%AE%E8%AE%BF%E9%97%AE-%E8%87%AA%E5%AE%9A%E4%B9%89%E6%96%B9%E5%BC%8F%E6%95%B4%E5%90%88druid%E6%95%B0%E6%8D%AE%E6%BA%90"><span class="toc-text">61、数据访问-自定义方式整合druid数据源</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Druid%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F"><span class="toc-text">Druid是什么？</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E8%87%AA%E5%AE%9A%E4%B9%89%E6%96%B9%E5%BC%8F"><span class="toc-text">自定义方式</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#62%E3%80%81%E6%95%B0%E6%8D%AE%E8%AE%BF%E9%97%AE-druid%E6%95%B0%E6%8D%AE%E6%BA%90starter%E6%95%B4%E5%90%88%E6%96%B9%E5%BC%8F"><span class="toc-text">62、数据访问-druid数据源starter整合方式</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#63%E3%80%81%E6%95%B0%E6%8D%AE%E8%AE%BF%E9%97%AE-%E6%95%B4%E5%90%88MyBatis-%E9%85%8D%E7%BD%AE%E7%89%88"><span class="toc-text">63、数据访问-整合MyBatis-配置版</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%B0%8F%E7%BB%93"><span class="toc-text">小结</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#64%E3%80%81%E6%95%B0%E6%8D%AE%E8%AE%BF%E9%97%AE-%E6%95%B4%E5%90%88MyBatis-%E6%B3%A8%E8%A7%A3%E9%85%8D%E7%BD%AE%E6%B7%B7%E5%90%88%E7%89%88"><span class="toc-text">64、数据访问-整合MyBatis-注解配置混合版</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#65%E3%80%81%E6%95%B0%E6%8D%AE%E8%AE%BF%E9%97%AE-%E6%95%B4%E5%90%88MyBatisPlus%E6%93%8D%E4%BD%9C%E6%95%B0%E6%8D%AE%E5%BA%93"><span class="toc-text">65、数据访问-整合MyBatisPlus操作数据库</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#MyBatisPlus%E6%98%AF%E4%BB%80%E4%B9%88"><span class="toc-text">MyBatisPlus是什么</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#66%E3%80%81%E6%95%B0%E6%8D%AE%E8%AE%BF%E9%97%AE-CRUD%E5%AE%9E%E9%AA%8C-%E6%95%B0%E6%8D%AE%E5%88%97%E8%A1%A8%E5%B1%95%E7%A4%BA"><span class="toc-text">66、数据访问-CRUD实验-数据列表展示</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#67%E3%80%81%E6%95%B0%E6%8D%AE%E8%AE%BF%E9%97%AE-CRUD%E5%AE%9E%E9%AA%8C-%E5%88%86%E9%A1%B5%E6%95%B0%E6%8D%AE%E5%B1%95%E7%A4%BA"><span class="toc-text">67、数据访问-CRUD实验-分页数据展示</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#68%E3%80%81%E6%95%B0%E6%8D%AE%E8%AE%BF%E9%97%AE-CRUD%E5%AE%9E%E9%AA%8C-%E5%88%A0%E9%99%A4%E7%94%A8%E6%88%B7%E5%AE%8C%E6%88%90"><span class="toc-text">68、数据访问-CRUD实验-删除用户完成</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#69%E3%80%81%E6%95%B0%E6%8D%AE%E8%AE%BF%E9%97%AE-%E5%87%86%E5%A4%87%E9%98%BF%E9%87%8C%E4%BA%91Redis%E7%8E%AF%E5%A2%83"><span class="toc-text">69、数据访问-准备阿里云Redis环境</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#70%E3%80%81%E6%95%B0%E6%8D%AE%E8%AE%BF%E9%97%AE-Redis%E6%93%8D%E4%BD%9C%E4%B8%8E%E7%BB%9F%E8%AE%A1%E5%B0%8F%E5%AE%9E%E9%AA%8C"><span class="toc-text">70、数据访问-Redis操作与统计小实验</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#71%E3%80%81%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95-JUnit5%E7%AE%80%E4%BB%8B"><span class="toc-text">71、单元测试-JUnit5简介</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#72%E3%80%81%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95-%E5%B8%B8%E7%94%A8%E6%B5%8B%E8%AF%95%E6%B3%A8%E8%A7%A3"><span class="toc-text">72、单元测试-常用测试注解</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#73%E3%80%81%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95-%E6%96%AD%E8%A8%80%E6%9C%BA%E5%88%B6"><span class="toc-text">73、单元测试-断言机制</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E7%AE%80%E5%8D%95%E6%96%AD%E8%A8%80"><span class="toc-text">简单断言</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%95%B0%E7%BB%84%E6%96%AD%E8%A8%80"><span class="toc-text">数组断言</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E7%BB%84%E5%90%88%E6%96%AD%E8%A8%80"><span class="toc-text">组合断言</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%BC%82%E5%B8%B8%E6%96%AD%E8%A8%80"><span class="toc-text">异常断言</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E8%B6%85%E6%97%B6%E6%96%AD%E8%A8%80"><span class="toc-text">超时断言</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%BF%AB%E9%80%9F%E5%A4%B1%E8%B4%A5"><span class="toc-text">快速失败</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#74%E3%80%81%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95-%E5%89%8D%E7%BD%AE%E6%9D%A1%E4%BB%B6"><span class="toc-text">74、单元测试-前置条件</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#75%E3%80%81%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95-%E5%B5%8C%E5%A5%97%E6%B5%8B%E8%AF%95"><span class="toc-text">75、单元测试-嵌套测试</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#76%E3%80%81%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95-%E5%8F%82%E6%95%B0%E5%8C%96%E6%B5%8B%E8%AF%95"><span class="toc-text">76、单元测试-参数化测试</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E8%BF%81%E7%A7%BB%E6%8C%87%E5%8D%97"><span class="toc-text">迁移指南</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#77%E3%80%81%E6%8C%87%E6%A0%87%E7%9B%91%E6%8E%A7-SpringBoot-Actuator%E4%B8%8EEndpoint"><span class="toc-text">77、指标监控-SpringBoot Actuator与Endpoint</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8"><span class="toc-text">如何使用</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#78%E3%80%81%E6%8C%87%E6%A0%87%E7%9B%91%E6%8E%A7-%E5%B8%B8%E4%BD%BF%E7%94%A8%E7%9A%84%E7%AB%AF%E7%82%B9%E5%8F%8A%E5%BC%80%E5%90%AF%E4%B8%8E%E7%A6%81%E7%94%A8"><span class="toc-text">78、指标监控-常使用的端点及开启与禁用</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%B8%B8%E4%BD%BF%E7%94%A8%E7%9A%84%E7%AB%AF%E7%82%B9"><span class="toc-text">常使用的端点</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Health-Endpoint"><span class="toc-text">Health Endpoint</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Metrics-Endpoint"><span class="toc-text">Metrics Endpoint</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%BC%80%E5%90%AF%E4%B8%8E%E7%A6%81%E7%94%A8Endpoints"><span class="toc-text">开启与禁用Endpoints</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%9A%B4%E9%9C%B2Endpoints"><span class="toc-text">暴露Endpoints</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#79%E3%80%81%E6%8C%87%E6%A0%87%E7%9B%91%E6%8E%A7-%E5%AE%9A%E5%88%B6Endpoint"><span class="toc-text">79、指标监控-定制Endpoint</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%AE%9A%E5%88%B6-Health-%E4%BF%A1%E6%81%AF"><span class="toc-text">定制 Health 信息</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%AE%9A%E5%88%B6info%E4%BF%A1%E6%81%AF"><span class="toc-text">定制info信息</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%AE%9A%E5%88%B6Metrics%E4%BF%A1%E6%81%AF"><span class="toc-text">定制Metrics信息</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%AE%9A%E5%88%B6Endpoint"><span class="toc-text">定制Endpoint</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#80%E3%80%81%E6%8C%87%E6%A0%87%E7%9B%91%E6%8E%A7-Boot-Admin-Server"><span class="toc-text">80、指标监控-Boot Admin Server</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#81%E3%80%81%E9%AB%98%E7%BA%A7%E7%89%B9%E6%80%A7-Profile%E7%8E%AF%E5%A2%83%E5%88%87%E6%8D%A2"><span class="toc-text">81、高级特性-Profile环境切换</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Profile%E6%9D%A1%E4%BB%B6%E8%A3%85%E9%85%8D%E5%8A%9F%E8%83%BD"><span class="toc-text">@Profile条件装配功能</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#82%E3%80%81%E9%AB%98%E7%BA%A7%E7%89%B9%E6%80%A7-%E9%85%8D%E7%BD%AE%E5%8A%A0%E8%BD%BD%E4%BC%98%E5%85%88%E7%BA%A7"><span class="toc-text">82、高级特性-配置加载优先级</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%A4%96%E9%83%A8%E5%8C%96%E9%85%8D%E7%BD%AE"><span class="toc-text">外部化配置</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#83%E3%80%81%E9%AB%98%E7%BA%A7%E7%89%B9%E6%80%A7-%E8%87%AA%E5%AE%9A%E4%B9%89starter%E7%BB%86%E8%8A%82"><span class="toc-text">83、高级特性-自定义starter细节</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#starter%E5%90%AF%E5%8A%A8%E5%8E%9F%E7%90%86"><span class="toc-text">starter启动原理</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E8%87%AA%E5%AE%9A%E4%B9%89starter"><span class="toc-text">自定义starter</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#84%E3%80%81%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E%90-SpringApplication%E5%88%9B%E5%BB%BA%E5%88%9D%E5%A7%8B%E5%8C%96%E6%B5%81%E7%A8%8B"><span class="toc-text">84、原理解析-SpringApplication创建初始化流程</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#SpringBoot%E5%90%AF%E5%8A%A8%E8%BF%87%E7%A8%8B"><span class="toc-text">SpringBoot启动过程</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#85%E3%80%81%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E%90-SpringBoot%E5%AE%8C%E6%95%B4%E5%90%AF%E5%8A%A8%E8%BF%87%E7%A8%8B"><span class="toc-text">85、原理解析-SpringBoot完整启动过程</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#86%E3%80%81%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E%90-%E8%87%AA%E5%AE%9A%E4%B9%89%E4%BA%8B%E4%BB%B6%E7%9B%91%E5%90%AC%E7%BB%84%E4%BB%B6"><span class="toc-text">86、原理解析-自定义事件监听组件</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#87%E3%80%81%E5%90%8E%E4%BC%9A%E6%9C%89%E6%9C%9F"><span class="toc-text">87、后会有期</span></a></li></ol></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div class="copyright">&copy;2022 - 2023 By zcm</div><div class="framework-info"><span>框架 </span><a target="_blank" rel="noopener" href="https://hexo.io">Hexo</a><span class="footer-separator">|</span><span>主题 </span><a target="_blank" rel="noopener" href="https://github.com/jerryc127/hexo-theme-butterfly">Butterfly</a></div></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="fas fa-book-open"></i></button><button id="darkmode" type="button" title="浅色和深色模式转换"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside_config" type="button" title="设置"><i class="fas fa-cog fa-spin"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="fas fa-list-ul"></i></button><button id="go-up" type="button" title="回到顶部"><i class="fas fa-arrow-up"></i></button></div></div><div id="local-search"><div class="search-dialog"><nav class="search-nav"><span class="search-dialog-title">搜索</span><span id="loading-status"></span><button class="search-close-button"><i class="fas fa-times"></i></button></nav><div class="is-center" id="loading-database"><i class="fas fa-spinner fa-pulse"></i><span>  数据库加载中</span></div><div class="search-wrap"><div id="local-search-input"><div class="local-search-box"><input class="local-search-box--input" placeholder="搜索文章" type="text"/></div></div><hr/><div id="local-search-results"></div></div></div><div id="search-mask"></div></div><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><script src="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox.umd.min.js"></script><script src="https://cdn.jsdelivr.net/npm/node-snackbar/dist/snackbar.min.js"></script><script src="/js/search/local-search.js"></script><div class="js-pjax"></div><script src="/js/categories.js?v1"></script><script src="/js/navigation.js?v1"></script><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script></div></body></html>